除了放置異步任務的事件,"任務隊列"還能夠放置定時事件,即指定某些代碼在多少時間以後執行。這叫作"定時器"(timer)功能,也就是定時執行的代碼。前端
定時器功能主要由setTimeout()
和setInterval()
這兩個函數來完成,它們的內部運行機制徹底同樣,區別在於前者指定的代碼是一次性執行,後者則爲反覆執行。如下主要討論setTimeout()。node
setTimeout()
接受兩個參數,第一個是回調函數
,第二個是推遲執行的毫秒數
。promise
console.log(1); setTimeout(function(){console.log(2);},1000); console.log(3);
上面代碼的執行結果是1,3,2,由於setTimeout()將第二行推遲到1000毫秒以後執行。瀏覽器
若是將setTimeout()的第二個參數設爲0,就表示當前代碼執行完(執行棧清空)之後,當即執行(0毫秒間隔)指定的回調函數。異步
setTimeout(function(){console.log(1);}, 0); console.log(2);
上面代碼的執行結果老是2,1,由於只有在執行完第二行之後,系統纔會去執行"任務隊列"中的回調函數。函數
總之,setTimeout(fn,0)的含義是,指定某個任務在主線程最先可得的空閒時間執行,也就是說,儘量早得執行。它在"任務隊列"的尾部添加一個事件,所以要等到同步任務和"任務隊列"現有的事件都處理完,纔會獲得執行。oop
HTML5標準規定了setTimeout()的第二個參數的最小值(最短間隔),不得低於4毫秒,若是低於這個值,就會自動增長。在此以前,老版本的瀏覽器都將最短間隔設爲10毫秒。另外,對於那些DOM的變更(尤爲是涉及頁面從新渲染的部分),一般不會當即執行,而是每16毫秒執行一次。這時使用requestAnimationFrame()的效果要好於setTimeout()。spa
須要注意的是,setTimeout()只是將事件插入了"任務隊列",必須等到當前代碼(執行棧)執行完,主線程纔會去執行它指定的回調函數
。要是當前代碼耗時很長,有可能要等好久,因此並無辦法保證,回調函數必定會在setTimeout()指定的時間執行。線程
Node.js也是單線程的Event Loop,可是它的運行機制不一樣於瀏覽器環境。
這裏須要注意一下,node新加了一個微任務(process.nextTick)
和一個宏任務(setImmediate)
簡單的來講,就是node在處理一個執行隊列的時候無論怎樣都會先執行完當前隊列,而後再清空微任務隊列,再去執行下一個隊列。code
請看下面的示意圖(做者@BusyRich)。
根據上圖,Node.js的運行機制以下。
(1)V8引擎解析JavaScript腳本。
(2)解析後的代碼,調用Node API。
(3)libuv庫負責Node API的執行。它將不一樣的任務分配給不一樣的線程,造成一個Event Loop(事件循環),以異步的方式將任務的執行結果返回給V8引擎。
(4)V8引擎再將結果返回給用戶。
除了setTimeout和setInterval這兩個方法,Node.js還提供了另外兩個與"任務隊列"有關的方法:process.nextTick和setImmediate。它們能夠幫助咱們加深對"任務隊列"的理解。
process.nextTick
方法能夠在當前"執行棧"的尾部----下一次Event Loop(主線程讀取"任務隊列")以前----觸發回調函數。也就是說,它指定的任務老是發生在全部異步任務以前
。setImmediate方法則是在當前"任務隊列"的尾部添加事件,也就是說,它指定的任務老是在下一次Event Loop時執行
,這與setTimeout(fn, 0)很像。請看下面的例子(via StackOverflow)。
process.nextTick(function A() { console.log(1); process.nextTick(function B(){console.log(2);}); }); setTimeout(function timeout() { console.log('TIMEOUT FIRED'); }, 0) // 1 // 2 // TIMEOUT FIRED
上面代碼中,因爲process.nextTick方法指定的回調函數,老是在當前"執行棧"的尾部觸發,因此不只函數A比setTimeout指定的回調函數timeout先執行,並且函數B也比timeout先執行。這說明,若是有多個process.nextTick語句(無論它們是否嵌套),將所有在當前"執行棧"執行。
如今,再看setImmediate。
setImmediate(function A() { console.log(1); setImmediate(function B(){console.log(2);}); }); setTimeout(function timeout() { console.log('TIMEOUT FIRED'); }, 0);
上面代碼中,setImmediate與setTimeout(fn,0)各自添加了一個回調函數A和timeout,都是在下一次Event Loop觸發。那麼,哪一個回調函數先執行呢?答案是不肯定。運行結果多是1--TIMEOUT FIRED--2,也多是TIMEOUT FIRED--1--2。
使人困惑的是,Node.js文檔中稱,setImmediate指定的回調函數,老是排在setTimeout前面。實際上,這種狀況只發生在遞歸調用的時候。
setImmediate(function (){ setImmediate(function A() { console.log(1); setImmediate(function B(){console.log(2);}); }); setTimeout(function timeout() { console.log('TIMEOUT FIRED'); }, 0); }); // 1 // TIMEOUT FIRED // 2
上面代碼中,setImmediate和setTimeout被封裝在一個setImmediate裏面,它的運行結果老是1--TIMEOUT FIRED--2,這時函數A必定在timeout前面觸發。至於2排在TIMEOUT FIRED的後面(即函數B在timeout後面觸發),是由於setImmediate老是將事件註冊到下一輪Event Loop,因此函數A和timeout是在同一輪Loop執行,而函數B在下一輪Loop執行。
咱們由此獲得了process.nextTick和setImmediate的一個重要區別:多個process.nextTick語句老是在當前"執行棧"一次執行完,多個setImmediate可能則須要屢次loop才能執行完
。事實上,這正是Node.js 10.0版添加setImmediate方法的緣由,不然像下面這樣的遞歸調用process.nextTick,將會沒完沒了,主線程根本不會去讀取"事件隊列"!
process.nextTick(function foo() { process.nextTick(foo); });
事實上,如今要是你寫出遞歸的process.nextTick,Node.js會拋出一個警告,要求你改爲setImmediate。
另外,因爲process.nextTick指定的回調函數是在本次"事件循環"觸發,而setImmediate指定的是在下次"事件循環"觸發,因此很顯然,前者老是比後者發生得早,並且執行效率也高(由於不用檢查"任務隊列")。
最後注意一下,微任務中process.nextTick比promise.then快
若是你以爲這篇文章對你有所幫助,那就順便點個贊吧,點點關注不迷路~
黑芝麻哇,白芝麻發,黑芝麻白芝麻哇發哈!
前端哇發哈