JavaScript定時器的更多知識點

来源:https://www.cnblogs.com/darecy/archive/2020/05/10/12863216.html
-Advertisement-
Play Games

導語:JavaScript定時器是window的一個對象介面,並不是JavaScript的一部分,它的功能是由瀏覽器實現的,在不同瀏覽器之間會有所不同。定時器也可以由node.js運行時本身實現。 幾周前我在推特上發佈了這樣一個面試問題: JavaScript面試問題: 在哪裡可以找到setTime ...


導語:JavaScript定時器是window的一個對象介面,並不是JavaScript的一部分,它的功能是由瀏覽器實現的,在不同瀏覽器之間會有所不同。定時器也可以由node.js運行時本身實現。

幾周前我在推特上發佈了這樣一個面試問題:

JavaScript面試問題:

在哪裡可以找到setTimeout和setInterval的源代碼?(他們在哪裡實現的?)

你怎麼在面試中回答?(你不能去網上搜索)

function setTimeOut(callback 

繼續往下看之前先試著回答這個問題

推特上半數的回答都是錯誤的 回答不是 V8 (或者其他虛擬機!!)儘管著名的“JavaScript定時器”函數像setTimeout 和 setInterval都不是ECMAScript規範或者任何JavaScript實現的一部分。 定時器功能由瀏覽器實現,它們的實現在不同瀏覽器之間會有所不同。定時器也可以由Node.js運行時本身實現。

在瀏覽器里主要的定時器函數是作為Window對象的介面,Window對象同時擁有很多其他方法和對象。該介面使其所有元素在JavaScript全局作用域中都可用。這就是為什麼你可以直接在瀏覽器控制台執行setTimeout

在node里,定時器是global對象的一部分,這點很像瀏覽器中的Window。你可以在Node里看到定時器的源碼 這裡.

有些人可能認為這是一個糟糕的面試問題 - 為什麼一定要知道這個問題呢?!作為一名JavaScript開發人員,我認為你應該知道這一點,因為如果你不這樣做,那可能表明你並不完全理解V8(和其他虛擬機)如何與瀏覽器和Node交互。

讓我們開始做一些關於定時器函數的例子和挑戰把?


更新: 這篇文章現在是“完整介紹Node.js”的一部分。 你可以在這裡閱讀最新的版本。


定時器函數是高階函數,可用於延遲或重覆執行其他函數(它們作為第一個參數接收)。

這是一個關於延遲的例子:

// example1.js
setTimeout(
  () => {
    console.log('Hello after 4 seconds');
  },
  4 * 1000
);

這個例子用setTimeout 延時4秒列印問候語。setTimeout的第二個參數是延時(多少毫秒)。這就是為什麼我用4*1000來表示4秒

setTimeout的第一個參數是一個將被延遲執行的函數

如果你在node環境執行 example1.js。Node將會暫停4秒然後列印問候語(接著退出)。

請註意,setTimeout的第一個參數只是一個函數引用。 它不必像example1.js那樣是內聯函數。 這是不使用內聯函數的相同示例:

const func = () => {
  console.log('Hello after 4 seconds');
};
setTimeout(func, 4 * 1000);

如果使用setTimeout延遲的函數需要攜帶參數,我們可以把參數放在setTimeout里(放在已知的兩個參數後)來中轉參數給需要延遲執行的函數。

// For: func(arg1, arg2, arg3, ...)
// We can use: setTimeout(func, delay, arg1, arg2, arg3, ...)

舉個例子:

// example2.js
const rocks = who => {
  console.log(who + ' rocks');
};
setTimeout(rocks, 2 * 1000, 'Node.js');

上面的rocks延遲2秒執行,接收who參數並且通過setTimeout中轉字元串“Node.js”給函數的who參數。

node環境執行example2.js控制台會在2秒後列印“Node.js rocks”

使用您到目前為止學到的關於setTimeout的知識,在相應的延遲後列印以下2條消息。

  • 4秒後列印消息“Hello after 4 seconds”
  • 8秒後列印“Hello after 8 seconds”消息。

約束:您只能在解決方案中定義一個函數,其中包括內聯函數。 這意味著許多setTimeout調用必須使用完全相同的函數。

解決方案

以下是我如何解決這一挑戰:

// solution1.js
const theOneFunc = delay => {
  console.log('Hello after ' + delay + ' seconds');
};
setTimeout(theOneFunc, 4 * 1000, 4);
setTimeout(theOneFunc, 8 * 1000, 8);

我讓theOneFunc收到一個delay參數,併在列印的消息中使用了delay參數的值。 這樣,該函數可以根據我們傳遞給它的任何延遲值列印不同的消息。

然後我在兩次setTimeout的調用中使用了theOneFunc,一個在4秒後觸發,另一個在8秒後觸發。 這兩個setTimeout調用也得到一個 第三個 參數來表示theOneFuncdelay參數。

使用node命令執行solution1.js文件將列印出挑戰要求的內容,4秒後的第一條消息和8秒後的第二條消息。

如果我要求你每隔4秒列印一條消息怎麼辦?

雖然你可以將setTimeout放在一個迴圈中,但定時器API也提供了setInterval函數,這將完成永遠做某事的要求。

這是setInterval的一個例子:

// example3.js
setInterval(
  () => console.log('Hello every 3 seconds'),
  3000
);

此示例將每3秒列印一次消息。 使用node命令執行example3.js將使Node永遠列印此消息,直到你終止該進程(使用CTRL + C)。

因為調用計時器函數會調度操作,所以在執行之前也可以取消該操作。

setTimeout的調用返回一個定時器“ID”,你可以使用帶有clearTimeout調用的定時器ID來取消該定時器。 下麵是這個例子:

// example4.js
const timerId = setTimeout(
  () => console.log('You will not see this one!'),
  0
);
clearTimeout(timerId);

這個簡單的計時器應該在“0”ms之後觸發(使其立即生效),但它不會因為我們正在捕獲timerId值併在使用clearTimeout調用後立即取消它。

當我們用node命令執行example4.js時,Node不會列印任何東西,進程就會退出。

順便說一句,在Node.js中,還有另一種方法可以使用0 ms來執行setTimeout。 Node.js計時器API有另一個名為setImmediate的函數,它與setTimeout基本相同,帶有0 ms但我們不必在那裡指定延遲:

setImmediate(
  () => console.log('I am equivalent to setTimeout with 0 ms'),
);

setImmediate方法在所有瀏覽器里都不支持。不要在前端代碼里使用它。

就像clearTimeout一樣,還有一個clearInterval函數,它對於setInerval調用執行相同的操作,並且還有一個clearImmediate調用。

在前面的例子中,您是否註意到在“0”ms之後執行帶有setTimeout的內容並不意味著立即執行它(在setTimeout行之後),而是在腳本中的所有其他內容之後立即執行它(包括clearTimeout調用)?

讓我用一個例子清楚地說明這一點。 這是一個簡單的setTimeout調用,應該在半秒後觸發,但它不會:

// example5.js
setTimeout(
  () => console.log('Hello after 0.5 seconds. MAYBE!'),
  500,
);
for (let i = 0; i < 1e10; i++) {
  // Block Things Synchronously
}

在此示例中定義計時器之後,我們使用大的for迴圈同步阻止運行時。 1e101後面有10個零,所以迴圈是一個10個十億滴答迴圈(基本上模擬繁忙的CPU)。 當此迴圈正在滴答時,節點無法執行任何操作。

這當然是在實踐中做的非常糟糕的事情,但它會幫助你理解setTimeout延遲不是一個保證的東西,而是一個最小的東西。 500ms表示最小延遲為500ms。 實際上,腳本將花費更長的時間來列印其問候語。 它必須等待阻塞迴圈才能完成。

編寫腳本每秒列印消息“ Hello World ”,但只列印5次。 5次之後,腳本應該列印消息“Done”並讓節點進程退出。

約束:你不能使用setTimeout調用來完成這個挑戰。 提示:你需要一個計數器。

解決方案

以下是我如何解決這個問題:

let counter = 0;
const intervalId = setInterval(() => {
  console.log('Hello World');
  counter += 1;
if (counter === 5) {
    console.log('Done');
    clearInterval(intervalId);
  }
}, 1000);

我將counter值作為0啟動,然後啟動一個setInterval調用同時捕獲它的id。

延遲功能將列印消息並每次遞增計數器。 在延遲函數內部,if語句將檢查我們現在是否處於5次。 如果是這樣,它將列印“Done”並使用捕獲的intervalId常量清除間隔。 間隔延遲為“1000”ms。

當你在常規函數中使用JavaScript的this關鍵字時,如下所示:

function whoCalledMe() {
  console.log('Caller is', this);
}

this關鍵字內的值將代表函數的調用者。 如果在Node REPL中定義上面的函數,則調用者將是global對象。 如果在瀏覽器的控制臺中定義函數,則調用者將是window對象。

讓我們將函數定義為對象的屬性,以使其更清晰:

const obj = { 
  id: '42',
  whoCalledMe() {
    console.log('Caller is', this);
  }
};
// The function reference is now: obj.whoCallMe

現在當你直接使用它的引用調用obj.whoCallMe函數時,調用者將是obj對象(由其id標識):

現在,問題是,如果我們將obj.whoCallMe的引用傳遞給setTimetout調用,調用者會是什麼?

//  What will this print??
setTimeout(obj.whoCalledMe, 0);

在這種情況下調用者會是誰?

答案根據執行計時器功能的位置而有所不同。 在這種情況下,你根本無法取決於調用者是誰。 你失去了對調用者的控制權,因為定時器實現將是現在調用您的函數的實現。 如果你在Node REPL中測試它,你會得到一個Timetout對象作為調用者:

請註意,這隻在您在常規函數中使用JavaScript的this關鍵字時才有意義。 如果您使用箭頭函數,則根本不需要擔心調用者。

編寫腳本以連續列印具有不同延遲的消息“Hello World”。 以1秒的延遲開始,然後每次將延遲增加1秒。 第二次將延遲2秒。 第三次將延遲3秒,依此類推。

在列印的消息中包含延遲時間。 預期輸出看起來像:

Hello World. 1
Hello World. 2
Hello World. 3...

約束:你只能使用const來定義變數。 你不能使用letvar

解決方案

因為延遲量是這個挑戰中的一個變數,我們不能在這裡使用setInterval,但我們可以在遞歸調用中使用setTimeout手動創建一個間隔執行。 使用setTimeout的第一個執行函數將創建另一個計時器,依此類推。

另外,因為我們不能使用let / var,所以我們不能有一個計數器來增加每個遞歸調用的延遲時間,但我們可以使用遞歸函數參數在遞歸調用期間遞增。

這是解決這一挑戰的一種可能方法:

const greeting = delay =>
  setTimeout(() => {
    console.log('Hello World. ' + delay);
    greeting(delay + 1);
  }, delay * 1000);
greeting(1);

編寫一個腳本以連續列印消息“Hello World”,其具有與挑戰#3相同的變化延遲概念,但這次是每個主延遲間隔的5個消息組。 從前5個消息的延遲100ms開始,接下來的5個消息延遲200ms,然後是300ms,依此類推。

以下是代碼的要求:

  • 在100ms點,腳本將開始列印“Hello World”,並以100ms的間隔進行5次。 第一條消息將出現在100毫秒,第二條消息將出現在200毫秒,依此類推。
  • 在前5條消息之後,腳本應將主延遲增加到200ms。 因此,第6條消息將在500毫秒+ 200毫秒(700毫秒)列印,第7條消息將在900毫秒列印,第8條消息將在1100毫秒列印,依此類推。
  • 在10條消息之後,腳本應將主延遲增加到300毫秒。 所以第11條消息應該在500ms + 1000ms + 300ms(18000ms)列印。 第12條消息應列印在21000ms,依此類推。
  • 一直重覆上面的模式。

在列印的消息中包含延遲。 預期的輸出看起來像這樣(沒有註釋):

Hello World. 100  // At 100ms
Hello World. 100  // At 200ms
Hello World. 100  // At 300ms
Hello World. 100  // At 400ms
Hello World. 100  // At 500ms
Hello World. 200  // At 700ms
Hello World. 200  // At 900ms
Hello World. 200  // At 1100ms...

約束:您只能使用setInterval調用(而不是setTimeout),並且只能使用一個if語句。

解決方案

因為我們只能使用setInterval調用,所以我們還需要遞歸來增加下一個setInterval調用的延遲。 另外,我們需要一個if語句來控制只有在5次調用該遞歸函數之後才能執行此操作。

以下是其中一種解決方案:

let lastIntervalId, counter = 5;
const greeting = delay => {
  if (counter === 5) {
    clearInterval(lastIntervalId);
    lastIntervalId = setInterval(() => {
      console.log('Hello World. ', delay);
      greeting(delay + 100);
    }, delay);
    counter = 0;
  }
counter += 1;
};
greeting(100);

謝謝閱讀。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • NDK clang編譯器的一個bug 問題代碼 ...
  • 本系列的目的是幫助更多面試經驗不足的前端人才更好地展現自己。在此,我分享一些以往我參加面試和參與招聘的一些心得,希望對大家有幫助。 關於簡歷設計 簡歷是人才的縮影,一份優質的簡歷是前往大公司的敲門磚。所以對於招聘,簡歷準備是第一環,也是最重要的一環。前端工程師的簡歷其實不需要視覺設計類的那般花哨,核 ...
  • 項目實現:還原百度搜索功能; 項目原理:利用json回調頁面傳參; 什麼是jsonp:就是利用<script>標簽的src地址,讓目標頁面回調本地頁面,並且帶入參數,也解決了跨域問題; 代碼如下: html(css代碼不提供) 1 <div class="box"> 2 <input type="t ...
  • 因為國內防火牆的原因,建議首先安裝 cnpm: 使用 npm install cnpm -g 或者 npm install -g cnpm --registry=https://registry.npm.taobao.org 【註】G:\MyWeb\...處為自定義的文件夾地址 安裝 webpack ...
  • # jQuery中的clone()和data()方法 - clone() ```js $('.demo').clone().appendTo('body');//把.demo元素一級元素的行間樣式複製一下添加到body元素下,但是克隆不了綁定的事件 $('.demo').clone(true).ap ...
  • # jQuery操作DOM 和 增刪改查 - 1.html() ```js $('ul li').html();//獲取ul下第一個li元素下的內容 $('ul li').html('9');//把ul下所有li元素下的內容改為9 $('ul li').html(function(index, el ...
  • 移動web最佳viewport設置 <meta name="viewport" content="width=device-width,initial-scale=1,user-scalable=no"> 單行文本溢出 .inaline{ overflow:hidden; white-space:n ...
  • 示例代碼托管在: "http://www.github.com/dashnowords/blogs" 博客園地址: "《大史住在大前端》原創博文目錄" 華為雲社區地址: "【你要的前端打怪升級指南】" [TOC] 原文地址: " https://threejsfundamentals.org/thr ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...