javascript運行機制

阮一峯的網絡日誌 » 首頁 » 檔案javascript

搜索
上一篇:喬布斯的管理課
下一篇:編譯器的工做過程
分類: JavaScript
JavaScript 運行機制詳解:再談Event Loop
做者: 阮一峯css

日期: 2014年10月 8日前端

珠峯培訓java

一年前,我寫了一篇《什麼是 Event Loop?》,談了我對Event Loop的理解。node

上個月,我偶然看到了Philip Roberts的演講《Help, I'm stuck in an event-loop》。這才尷尬地發現,本身的理解是錯的。我決定重寫這個題目,詳細、完整、正確地描述JavaScript引擎的內部運行機制。下面就是個人重寫。linux

進入正文以前,插播一條消息。個人新書《ECMAScript 6入門》出版了(版權頁,內頁1,內頁2),銅版紙全綵印刷,很是精美,還附有索引(固然價格也比同類書籍略貴一點點)。預覽和購買點擊這裏。git

cover程序員

(2014年10月13日更新:本文已經作了較大修改,反映了我如今的認識。關於setTimeout的更多解釋和示例,請參閱我正在寫的《JavaScript標準參考教程》。)es6

(2014年10月11日更新:樸靈老師對本文作了評註,詳細得指出了文中存在的錯誤說法,建議閱讀。)github

1、爲何JavaScript是單線程?
JavaScript語言的一大特色就是單線程,也就是說,同一個時間只能作一件事。那麼,爲何JavaScript不能有多個線程呢?這樣能提升效率啊。

JavaScript的單線程,與它的用途有關。做爲瀏覽器腳本語言,JavaScript的主要用途是與用戶互動,以及操做DOM。這決定了它只能是單線程,不然會帶來很複雜的同步問題。好比,假定JavaScript同時有兩個線程,一個線程在某個DOM節點上添加內容,另外一個線程刪除了這個節點,這時瀏覽器應該以哪一個線程爲準?

因此,爲了不復雜性,從一誕生,JavaScript就是單線程,這已經成了這門語言的核心特徵,未來也不會改變。

爲了利用多核CPU的計算能力,HTML5提出Web Worker標準,容許JavaScript腳本建立多個線程,可是子線程徹底受主線程控制,且不得操做DOM。因此,這個新標準並無改變JavaScript單線程的本質。

2、任務隊列
單線程就意味着,全部任務須要排隊,前一個任務結束,纔會執行後一個任務。若是前一個任務耗時很長,後一個任務就不得不一直等着。

若是排隊是由於計算量大,CPU忙不過來,倒也算了,可是不少時候CPU是閒着的,由於IO設備(輸入輸出設備)很慢(好比Ajax操做從網絡讀取數據),不得不等着結果出來,再往下執行。

JavaScript語言的設計者意識到,這時主線程徹底能夠無論IO設備,掛起處於等待中的任務,先運行排在後面的任務。等到IO設備返回告終果,再回過頭,把掛起的任務繼續執行下去。

因而,全部任務能夠分紅兩種,一種是同步任務(synchronous),另外一種是異步任務(asynchronous)。同步任務指的是,在主線程上排隊執行的任務,只有前一個任務執行完畢,才能執行後一個任務;異步任務指的是,不進入主線程、而進入"任務隊列"(task queue)的任務,只有"任務隊列"通知主線程,某個異步任務能夠執行了,該任務纔會進入主線程執行。

具體來講,異步執行的運行機制以下。(同步執行也是如此,由於它能夠被視爲沒有異步任務的異步執行。)

(1)全部同步任務都在主線程上執行,造成一個執行棧(execution context stack)。

(2)主線程以外,還存在一個"任務隊列"(task queue)。只要異步任務有了運行結果,就在"任務隊列"之中放置一個事件。

(3)一旦"執行棧"中的全部同步任務執行完畢,系統就會讀取"任務隊列",看看裏面有哪些事件。那些對應的異步任務,因而結束等待狀態,進入執行棧,開始執行。

(4)主線程不斷重複上面的第三步。

下圖就是主線程和任務隊列的示意圖。

任務隊列

只要主線程空了,就會去讀取"任務隊列",這就是JavaScript的運行機制。這個過程會不斷重複。

3、事件和回調函數
"任務隊列"是一個事件的隊列(也能夠理解成消息的隊列),IO設備完成一項任務,就在"任務隊列"中添加一個事件,表示相關的異步任務能夠進入"執行棧"了。主線程讀取"任務隊列",就是讀取裏面有哪些事件。

"任務隊列"中的事件,除了IO設備的事件之外,還包括一些用戶產生的事件(好比鼠標點擊、頁面滾動等等)。只要指定過回調函數,這些事件發生時就會進入"任務隊列",等待主線程讀取。

所謂"回調函數"(callback),就是那些會被主線程掛起來的代碼。異步任務必須指定回調函數,當主線程開始執行異步任務,就是執行對應的回調函數。

"任務隊列"是一個先進先出的數據結構,排在前面的事件,優先被主線程讀取。主線程的讀取過程基本上是自動的,只要執行棧一清空,"任務隊列"上第一位的事件就自動進入主線程。可是,因爲存在後文提到的"定時器"功能,主線程首先要檢查一下執行時間,某些事件只有到了規定的時間,才能返回主線程。

4、Event Loop
主線程從"任務隊列"中讀取事件,這個過程是循環不斷的,因此整個的這種運行機制又稱爲Event Loop(事件循環)。

爲了更好地理解Event Loop,請看下圖(轉引自Philip Roberts的演講《Help, I'm stuck in an event-loop》)。

Event Loop

上圖中,主線程運行的時候,產生堆(heap)和棧(stack),棧中的代碼調用各類外部API,它們在"任務隊列"中加入各類事件(click,load,done)。只要棧中的代碼執行完畢,主線程就會去讀取"任務隊列",依次執行那些事件所對應的回調函數。

執行棧中的代碼(同步任務),老是在讀取"任務隊列"(異步任務)以前執行。請看下面這個例子。


var req = new XMLHttpRequest();
req.open('GET', url);
req.onload = function (){};
req.onerror = function (){};
req.send();
上面代碼中的req.send方法是Ajax操做向服務器發送數據,它是一個異步任務,意味着只有當前腳本的全部代碼執行完,系統纔會去讀取"任務隊列"。因此,它與下面的寫法等價。


var req = new XMLHttpRequest();
req.open('GET', url);
req.send();
req.onload = function (){};
req.onerror = function (){};
也就是說,指定回調函數的部分(onload和onerror),在send()方法的前面或後面可有可無,由於它們屬於執行棧的一部分,系統老是執行完它們,纔會去讀取"任務隊列"。

5、定時器
除了放置異步任務的事件,"任務隊列"還能夠放置定時事件,即指定某些代碼在多少時間以後執行。這叫作"定時器"(timer)功能,也就是定時執行的代碼。

定時器功能主要由setTimeout()和setInterval()這兩個函數來完成,它們的內部運行機制徹底同樣,區別在於前者指定的代碼是一次性執行,後者則爲反覆執行。如下主要討論setTimeout()。

setTimeout()接受兩個參數,第一個是回調函數,第二個是推遲執行的毫秒數。


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)的含義是,指定某個任務在主線程最先可得的空閒時間執行,也就是說,儘量早得執行。它在"任務隊列"的尾部添加一個事件,所以要等到同步任務和"任務隊列"現有的事件都處理完,纔會獲得執行。

HTML5標準規定了setTimeout()的第二個參數的最小值(最短間隔),不得低於4毫秒,若是低於這個值,就會自動增長。在此以前,老版本的瀏覽器都將最短間隔設爲10毫秒。另外,對於那些DOM的變更(尤爲是涉及頁面從新渲染的部分),一般不會當即執行,而是每16毫秒執行一次。這時使用requestAnimationFrame()的效果要好於setTimeout()。

須要注意的是,setTimeout()只是將事件插入了"任務隊列",必須等到當前代碼(執行棧)執行完,主線程纔會去執行它指定的回調函數。要是當前代碼耗時很長,有可能要等好久,因此並無辦法保證,回調函數必定會在setTimeout()指定的時間執行。

6、Node.js的Event Loop
Node.js也是單線程的Event Loop,可是它的運行機制不一樣於瀏覽器環境。

請看下面的示意圖(做者@BusyRich)。

Node.js

根據上圖,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指定的是在下次"事件循環"觸發,因此很顯然,前者老是比後者發生得早,並且執行效率也高(由於不用檢查"任務隊列")。

(完)

文檔信息
版權聲明:自由轉載-非商用-非衍生-保持署名(創意共享3.0許可證)
發表日期: 2014年10月 8日
更多內容: 檔案 » JavaScript
文集:《前方的路》,《將來世界的倖存者》
社交媒體: twitter, weibo


相關文章
2017.09.19: 《ES6 標準入門(第3版)》上市了!
2017年6月,TC39 委員會正式發佈了《ES2017 標準》。
2017.09.07: asm.js 和 Emscripten 入門教程
Web 技術日新月異,可是有一個領域一直沒法突破 ---- 遊戲。
2017.08.09: Koa 框架教程
Node 主要用在開發 Web 應用。這決定了使用 Node,每每離不開 Web 應用框架。
2017.04.16: JavaScript 內存泄漏教程
1、什麼是內存泄漏? 程序的運行須要內存。只要程序提出要求,操做系統或者運行時(runtime)就必須供給內存。
廣告(購買廣告位)
留言(124條)
ceclinux 說:

寫的不錯

2014年10月 8日 21:28 | # | 引用

wuwenjie 說:

補充:Cluster是node js 多進程的API,是提升js資源使用效率的一種方法。
API描述:A single instance of Node runs in a single thread. To take advantage of multi-core systems the user will sometimes want to launch a cluster of Node processes to handle the load.

2014年10月 8日 21:50 | # | 引用

Tim 說:

簡言之就是JS只有一個主線程,主線程執行完執行棧的任務後去檢查異步的任務隊列,若是異步事件觸發,則將其加到主線程的執行棧。

2014年10月 8日 22:24 | # | 引用

jruif 說:

在https://medium.com/@shijuvar/web-development-trends-for-2015-and-beyond-c2d3c1ef5718這篇文章中的關於nodejs的可維護性、可用性和高性能的論證不知道阮一峯大哥怎麼看?

2014年10月 8日 23:27 | # | 引用

jare 說:

今天發現#atom-shell#作同步ipc調用時,執行棧是可能被掛起的,至關於主線程sleep了,等ipc返回後纔會接着執行後續代碼。若是掛起的時候同一段代碼被event又一次觸發,邏輯就可能出錯。

2014年10月 9日 00:41 | # | 引用

jare 說:

也就是說執行棧可能不能在一個loop內被執行完,由於會被掛起,是否掛起取決於運行環境和執行的操做種類。被掛起後前面註冊的callback仍是有可能先於執行棧剩餘的代碼觸發。

2014年10月 9日 00:43 | # | 引用

rednax 說:

補充一下Timer相關的問題:
1. 其實並非「有些瀏覽器規定了setTimeout 的最小時間間隔」,這個最小時間間隔是W3C在HTML標準中規定,規定要求低於4ms的時間間隔算爲4ms。
2. process.nextTick在最新版nodejs中不被推薦使用,推薦使用setImmediate ,緣由在於nextTick是在當前幀介紹後當即執行,會阻斷IO而且有最大數量限制;而setImmediate不會阻斷IO,更像是setTimeout(func, 0)

2014年10月 9日 01:06 | # | 引用

阮一峯 說:

@rednax:

謝謝指出,我還不知道HTML5把4毫秒寫入了標準,已經改過來了。

我查了Node.js 10.0的發佈說明( http://blog.nodejs.org/2013/03/11/node-v0-10-0-stable/ ),對於process和setImmediate的差別,還在消化當中。

2014年10月 9日 06:31 | # | 引用

nimeiz 說:

(3)libuv庫負責Node API的執行。它將不一樣的任務分配給不一樣的線程,造成一個Event Loop(事件循環),以異步的方式將任務的執行結果返回給V8引擎。
不一樣任務會分給不一樣線程?不是直接調用相關操做系統接口?

2014年10月 9日 08:37 | # | 引用

twang 說:

我想轉載這篇博客,能夠嗎?

2014年10月 9日 09:11 | # | 引用

roast_soul 說:

我以爲單線程的異步沒什麼意義。你看一下這個文章:

http://cnn237111.blog.51cto.com/2359144/1556987
2014年10月 9日 09:37 | # | 引用

萬能小新 說:

requestAnimFrame應該是requestAnimationFrame吧。

2014年10月 9日 09:39 | # | 引用

chptx 說:

單線程效率高是假,簡化開發是真。
跟GUI相關的框架的都採用這種模型,QT、GTK、MFC、Android UI框架、這裏的JS都是這樣的。

2014年10月 9日 12:02 | # | 引用

numbbbbb 說:

setImmediate的例子有問題吧,觸發A以後並非直接輸出1,而是執行另外一個setImmediate,這個setImmediate會把B添加到任務隊列尾部,在執行B以前會觸發setTimeout的回調,因此例子的輸出順序應該是:

// TIMEOUT FIRED
// 1
// 2
2014年10月 9日 22:52 | # | 引用

阮一峯 說:

@numbbbbb:

謝謝指出,你說得對,例子有錯,已經改過來了。

2014年10月 9日 23:11 | # | 引用

阮一峯 說:

引用萬能小新的發言:
requestAnimFrame應該是requestAnimationFrame吧。

謝謝指出,改過來了。

2014年10月 9日 23:39 | # | 引用

Leo 說:

setImmediate(function A() {
console.log(1);
setImmediate(function B(){console.log(2);});
});

setTimeout(function timeout() {
console.log('TIMEOUT FIRED');
}, 0)
// 1
// TIMEOUT FIRED
// 2
===========
一樣的這段代碼, 爲何後邊的說 Immediate 和 Timeout 的前後順序不肯定呢? 求解釋...

2014年10月10日 17:38 | # | 引用

mamamiya 說:

https://app.yinxiang.com/shard/s8/sh/b72fe246-a89d-434b-85f0-a36420849b84/59bad790bdcf6b0a66b8b93d5eacbead

樸靈對您文章的評註。語言雖然略顯犀利,可是我的以爲評註的應該是對的。

2014年10月10日 19:03 | # | 引用

阮一峯 說:

@Leo:

謝謝指出,我修改文章時,腦子糊塗了,已經改過來了。

2014年10月10日 19:22 | # | 引用

csu 說:

https://app.yinxiang.com/shard/s8/sh/b72fe246-a89d-434b-85f0-a36420849b84/59bad790bdcf6b0a66b8b93d5eacbead阮老師,有一位高手(前端專家樸靈)對你的這篇文章提出了很多見解,你能夠看看。

2014年10月10日 20:22 | # | 引用

Dean 說:

https://app.yinxiang.com/shard/s8/sh/b72fe246-a89d-434b-85f0-a36420849b84/59bad790bdcf6b0a66b8b93d5eacbead

阮老師 您被打臉了

2014年10月11日 09:32 | # | 引用

SO 說:

門都還沒入就來寫科普

2014年10月11日 09:39 | # | 引用

BlueiceQ 說:

https://app.yinxiang.com/shard/s8/sh/b72fe246-a89d-434b-85f0-a36420849b84/59bad790bdcf6b0a66b8b93d5eacbead

阮老師,看了沒?

2014年10月11日 10:51 | # | 引用

金戈鐵馬 說:

打人不打臉,絕交。。

2014年10月11日 14:50 | # | 引用

rednax 說:

引用mamamiya的發言:
https://app.yinxiang.com/shard/s8/sh/b72fe246-a89d-434b-85f0-a36420849b84/59bad790bdcf6b0a66b8b93d5eacbead

樸靈對您文章的評註。語言雖然略顯犀利,可是我的以爲評註的應該是對的。

看了一下不以爲評註對到哪裏去,只有吹毛求疵之感。 好比同步異步介紹,原本就無大錯;好比node圖裏面的OS operation,推敲一下就能夠猜到那是指同步操做(天然不走event loop了);至於watcher啥的,顯然只是實現上的特點,即便用同一個queue實現也何嘗不可。
2014年10月12日 16:39 | # | 引用

zzm 說:

我去,被樸靈狠劈,如今更亂了

2014年10月12日 19:48 | # | 引用

牛牛 說:

請問你一個問題,你的主業是金融,爲何還花這麼多時間在編程上?即便你明知道無論你如何努力地來提升本身的編程水平,也沒法改變業餘的地位,也得不到什麼實際的回報。

2014年10月12日 20:03 | # | 引用

zzm 說:

引用rednax的發言:

看了一下不以爲評註對到哪裏去,只有吹毛求疵之感。
好比同步異步介紹,原本就無大錯;好比node圖裏面的OS operation,推敲一下就能夠猜到那是指同步操做(天然不走event loop了);至於watcher啥的,顯然只是實現上的特點,即便用同一個queue實現也何嘗不可。

多看幾眼還真是

2014年10月12日 20:09 | # | 引用

淘傑 說:

"setImmediate的另外一個重要特色:一次"事件循環"只能觸發一個由setImmediate指定的回調函數。"
這個說法其實也不許確,準確的說是由於setImmediate是在事件循環結束後觸發,

setImmediate(function A() {
console.log(1);
setImmediate(function B(){console.log(2);});
});

setTimeout(function timeout() {
console.log('TIMEOUT FIRED');
}, 0);
當第一個loop結束後觸發第一次setImmediate,此時setImmediate調用以後開始將回調函數加入隊列等待下一個loop結束觸發, 而不是一次"事件循環"只能觸發一個由setImmediate指定的回調函數。多個只要在loop前已經執行掉一樣會在一次"事件循環"中一塊兒觸發。
setImmediate(function A() {
console.log(1);
});
setImmediate(function B(){console.log(2);});
實際上,上面調用2次setImmediate會造成一個immediateQueue鏈表,loop結束後會執行改鏈表裏全部的函數~
2014年10月12日 22:47 | # | 引用

淘傑 說:

引用Leo的發言:
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 和 Timeout 的前後順序不肯定呢? 求解釋...

@Leo , node中的底層loop有3種運行模式,DEFAULT, UV_RUN_ONCE,UV_RUN_NOWAIT具體差別詳見libuv文檔 。 剛啓動node解析執行腳本時以UV_RUN_ONCE的模式執行也就是說第一次loop是UV_RUN_ONCE模式,在每個loop中先檢測timers,而後進入io_poll此時會阻塞等待事件發生,事件發生後執行setImmediate中的回調,當以UV_RUN_ONCE模式執行的時候會再次檢測timers 。照這個原理來講,setImmediate應該比setTimeout先執行。

setTimeout(fn,0) 底層實際執行的是 setTimeout(fn,1), 此時會向loop隊列中註冊一個超時事件,假設註冊時當前時間戳是1000,它的過時時間就是1001。按上面的說法1ms 第一次loop執行完後先執行setImmediate回調,再執行timer回調。可是有一點別忘了,您的計算機此時還有其餘任務在處理,cpu不是所有用來跑你的node進程的,也就是說雖然cpu很快,但執行指令總得花點時間吧,從setTimeout(fn,0)註冊一個1ms的超時事件到第一次執行timers可能就已經花了1ms的時間,此時系統時間已經到1001了,那麼對應的timer回調固然就會先於setImmediate執行,因此說Immediate 和 Timeout 的前後順序不肯定。

2014年10月12日 23:08 | # | 引用

阮一峯 說:

引用淘傑的發言:
當第一個loop結束後觸發第一次setImmediate,此時setImmediate調用以後開始將回調函數加入隊列等待下一個loop結束觸發, 而不是一次"事件循環"只能觸發一個由setImmediate指定的回調函數。多個只要在loop前已經執行掉一樣會在一次"事件循環"中一塊兒觸發。
謝謝指出,確實如此,已經改正了。
2014年10月13日 09:11 | # | 引用

crazy 說:

究竟誰對誰錯呢?如今有些混亂了

2014年10月13日 09:31 | # | 引用

Jak Wings 說:

引用crazy的發言:
究竟誰對誰錯呢?如今有些混亂了

普通應用不必這麼吹毛球疵,只要瞭解一下 process.nextTick 和 setImmediate 的區別就行了,官方的 API 說明已經解釋得很好,帶實際應用示例。阮先生沒有把官方介紹的 process.nextTick 實際用例放上來直接硬剝是有點無趣了。

2014年10月13日 09:39 | # | 引用

Jak Wings 說:

咱們由此獲得了process.nextTick和setImmediate的一個重要區別……事實上,這正是Node.js 10.0版添加setImmediate方法的緣由,不然像下面這樣的遞歸調用process.nextTick,將會沒完沒了……
不知道哪裏來的「事實上」,官方示例已經明確地說「This is not a simple alias to setTimeout(fn, 0)」了,固然也更不可能本來做爲 setImmediate 的替代,或者反過來。process.nextTick 是有特殊用途的,並仍是單單脫離原有的 event loop。

2014年10月13日 09:46 | # | 引用

Jak Wings 說:

引用Jak Wings的發言:
不知道哪裏來的「事實上」,官方示例已經明確地說「This is not a simple alias to setTimeout(fn, 0)」了,固然也更不可能本來做爲 setImmediate 的替代,或者反過來。process.nextTick 是有特殊用途的,並仍是單單脫離原有的 event loop。

囧,抱歉,纔看了官方發佈說明,看來 process.nextTick 是被濫用後才加上 setImmediate 的。不過我仍是以爲 API 說明頁面的說明比發佈那時的好,setImmediate 是和 setTimeout 使用同一個 event loop 的,應該凸出了和 process.nextTick 的不一樣了,應該也是後來提防你們濫用才這麼設定的。

2014年10月13日 09:56 | # | 引用

Jak Wings 說:

使人困惑的是,Node.js文檔中稱,setImmediate指定的回調函數,老是排在setTimeout前面。實際上,這種狀況只發生在遞歸調用的時候。
我剛剛用 v0.10.32 和 v0.11.14測試過了,這個也沒有保證。我能獲得 1-T...-2 和 1-2-T... 兩種結果,甚至是 T...-1-2。信不信由你。

2014年10月13日 10:14 | # | 引用

阮一峯 說:

引用Jak Wings的發言:

我剛剛用 v0.10.32 和 v0.11.14測試過了,這個也沒有保證。我能獲得 1-T...-2 和 1-2-T... 兩種結果,甚至是 T...-1-2。信不信由你。

我測的結果,一直都是1-T-2。難道這個也不肯定…… 

2014年10月13日 14:06 | # | 引用

Leo 說:

@淘傑:

謝謝解釋~

2014年10月13日 14:44 | # | 引用

林夕軒 說:

引用淘傑的發言:
setTimeout(fn,0) 底層實際執行的是 setTimeout(fn,1), 此時會向loop隊列中註冊一個超時事件,假設註冊時當前時間戳是1000,它的過時時間就是1001。按上面的說法1ms 第一次loop執行完後先執行setImmediate回調,再執行timer回調。可是有一點別忘了,您的計算機此時還有其餘任務在處理,cpu不是所有用來跑你的node進程的,也就是說雖然cpu很快,但執行指令總得花點時間吧,從setTimeout(fn,0)註冊一個1ms的超時事件到第一次執行timers可能就已經花了1ms的時間,此時系統時間已經到1001了,那麼對應的timer回調固然就會先於setImmediate執行,因此說Immediate 和 Timeout 的前後順序不肯定。

這麼解釋的確合理,定時器的原理都是如此,告訴cpu有個任務在某個時間應該執行。但也只有cpu有空的時候,才能儘快執行纔可。

2014年10月14日 11:10 | # | 引用

猴子 說:

讀完正文以爲不少東西是純理論的理解,有些可能來源於猜想。

這篇文章更該是一個學習筆記,文中提到的不少event其實和Javascript語言徹底無關,純粹是瀏覽器的處理和行爲。

對於任何一個不基於瀏覽器且支持多線程的程序來說(好比隨便一個C實現的遊戲),會發現一樣的事件處理好比鼠標操做、鍵盤快捷鍵等等,是須要去本身單獨實現的,那麼也許實現中使用了Event loop,但這一部分從未集成到語言中,對吧?

理解這個事情更好的辦法是真的去實現一個相似的程序。

2014年10月14日 16:37 | # | 引用

陳佳 說:

看來我須要買書好好學學了。

2014年10月16日 01:51 | # | 引用

Jak Wings 說:

引用阮一峯的發言:
我測的結果,一直都是1-T-2。難道這個也不肯定…… 

對了,V8 引擎能表明全部實現了 ES6 的瀏覽器的具體實現麼?

2014年10月16日 15:09 | # | 引用

阮一峯 說:

引用Jak Wings的發言:
V8 引擎能表明全部實現了 ES6 的瀏覽器的具體實現麼?

問題是Node採用了V8

2014年10月16日 17:33 | # | 引用

stackvoid 說:

的確有些說法不太嚴謹~~~

2014年10月16日 17:36 | # | 引用

愛因斯坦 說:

引用牛牛的發言:
請問你一個問題,你的主業是金融,爲何還花這麼多時間在編程上?即便你明知道無論你如何努力地來提升本身的編程水平,也沒法改變業餘的地位,也得不到什麼實際的回報。

請問你一個問題,你的主業是專利局小職員,爲何還花這麼多時間在物理上?即便你明知道無論你如何努力地來提升本身的數學水平,也沒法改變業餘的地位,也得不到什麼實際的回報。

2014年10月18日 03:45 | # | 引用

樸素博客 說:

引用愛因斯坦的發言:
請問你一個問題,你的主業是專利局小職員,爲何還花這麼多時間在物理上?即便你明知道無論你如何努力地來提升本身的數學水平,也沒法改變業餘的地位,也得不到什麼實際的回報。

由於熱愛 因此熱愛
2014年10月18日 17:18 | # | 引用

臘月乘涼 說:

阮大俠你就別再禍害程序員了,建議先補一下計算機組成原理,編譯原理,算法導論,C語言基礎再來出書,否則你出了也沒人敢看吧。另外起碼要實踐一點兒項目。

var req = new XMLHttpRequest();
req.open('GET', url);
req.onload = function (){};
req.onerror = function (){};
req.send();

var req = new XMLHttpRequest();
req.open('GET', url);
req.send();
req.onload = function (){};
req.onerror = function (){};

你說這倆等價,等價個毛線啊,req.send();以後若是瀏覽器有緩存這個函數會當即返回,也就是說無論你是onload仍是onerror都不會執行。要先綁定回調再去執行send(),這樣纔沒bug。

2014年10月22日 00:09 | # | 引用

jiangjunzhangas 說:

不過癮

2014年10月22日 22:02 | # | 引用

ARM Linux 說:

Js原來還有這麼深奧的功能

2014年10月28日 13:53 | # | 引用

小雪 說:

引用牛牛的發言:
請問你一個問題,你的主業是金融,爲何還花這麼多時間在編程上?即便你明知道無論你如何努力地來提升本身的編程水平,也沒法改變業餘的地位,也得不到什麼實際的回報。

阮老師已是程序員了……

2014年10月30日 19:38 | # | 引用

Hiccup 說:

不正確的文章爲何不刪掉

2014年10月31日 11:54 | # | 引用

reducm 說:

想不到阮老師去了阿里巴巴作前端阿

2014年10月31日 13:53 | # | 引用

渠德通 說:

很長一段時間,google日曆沒法訪問,但網上又找不到好用的在線日曆軟件,偶然間看到您開發的這個簡易版google日曆,發自心裏的高興,但全部的瀏覽器都用了,就是沒法聯接,聯的過程當中,顯示一個進度條,等到100%以後,立刻出現一個空白頁,提示沒法鏈接。鬱悶中!! 若是才能正常聯接,期待您的指點。個人郵箱爲:qudetong@www5a.com,手機:13501282988.若是可能能話,我願意爲您長期進行測試各項功能

2014年10月31日 15:10 | # | 引用

alex 說:

歡迎兔哥加入大阿里!

2014年10月31日 21:38 | # | 引用

陳明 說:

強烈建議博主開個微信公衆號!

2014年11月 2日 09:30 | # | 引用

Byron 說:

引用reducm的發言:
想不到阮老師去了阿里巴巴作前端阿

是爲了和樸靈PK纔來的麼?

2014年11月 3日 10:20 | # | 引用

花生大仙 說:

很久沒更新博客了,甚是想念。

2014年11月 3日 14:01 | # | 引用

趙秀芳 說:

據說阮先生,要去螞蟻金服了...
阮先生很久不更新了,要記得經常更新哦。

2014年11月 6日 11:05 | # | 引用

Steven 說:

@臘月乘涼:

確實是這樣的

2014年11月11日 00:14 | # | 引用

雁南飛 說:

有沒有辦法查看Function Object的internal property?

2014年11月25日 11:45 | # | 引用

felix 說:

有一個問題,事件執行的粒度是多大?是一個函數仍是一個語句塊?
在大量的異步方法同時操做一個變量的時候,會不會出現相似線程的問題呢?
2014年11月25日 11:57 | # | 引用

袁梓民 說:

var start = new Date();
console.log(start);

var print = setTimeout(function() {
var end = new Date();
console.log('從開始到執行這個setTimeout函數花掉的時間爲', end - start, "ms");
}, 1000);

var test = [];
for(var i = 0; i test.push(i);
}
var middle = new Date();
console.log('執行數組操做所花掉的時間爲:',middle - start, 'ms');

我想請教一下這裏的setTimeout會在何時執行?我再node裏面和瀏覽器裏面分別跑了幾回。獲得的結果是:
node:
執行數組操做所花掉的時間爲: 379 ms
從開始到執行這個setTimeout函數花掉的時間爲: 1030ms
按照您的說法,setTimeout應該是儘量在能執行的時候執行,這裏運算操做只是379ms,沒有超過初始設定的1000ms,按道理1030ms不該該是1001ms麼?
瀏覽器:
執行數組操做所花掉的時間爲: 8016 ms
從開始到執行這個setTimeout函數花掉的時間爲 8017 ms
這個結果中,運算時間很長,遠遠超過了1000ms,因此setTimeout不是在1001ms執行的,而是等到元算結束以後當即執行,那麼結果奇怪了,這個setTimeout時間設定成1000ms是從何時開始的?個人理解是一開始就計時,可是1000ms沒法執行,等到數組操做完成以後才能執行,這樣計時操做彷佛沒有存在的意義了。
2014年11月26日 14:14 | # | 引用

stubbornleaf 說:

原來水平也不過如此。有點水平的人都看得懂樸靈的批註比較準確。

2014年12月 2日 07:13 | # | 引用

rufushuang 說:

從這篇文章看得出,阮老師不是科班出身的
不過非科班的也有非科班的好,但願可以繼續有文章看

2014年12月11日 14:52 | # | 引用

水中月鏡中花 說:

阮老師,那若是在ajax的回調函數中,存在setTimeOut函數,這個時候回調函數中仍是按照Event loop的機制嗎?就是說回調函數至關於主線程,把setTimeOut函數放在任務對象,先執行回調函數的代碼再執行執行setTimeout函數?

2014年12月17日 14:59 | # | 引用

歐文 說:

阮老師,你的站點統計工具:specificclick好像不工做耶,請須知。

2015年1月26日 14:28 | # | 引用

linda 說:

對您的網站有一些小提議,供您參考。
一、給頁面右下角或者右側加一個按鈕,能夠直接跳到最頂層,這樣對讀者來講很方便,由於一直拖動滾動條很麻煩。同時,這個按鈕相對於瀏覽器窗口是固定的。
二、給標題下面加上您文章的寫做日期。做爲讀者我感受這個時間挺重要的。

2015年1月28日 15:43 | # | 引用

Taomax 說:

嗯,仍是比較實用的內容呢,我會長期關注的。

2015年2月 5日 20:48 | # | 引用

stupid 說:

請不要說你在淘寶工做好嗎?淘寶不都是大牛嗎?

2015年3月10日 11:22 | # | 引用

stupid 說:

淘寶的人都喜歡出書嗎?居然都敢出書?

2015年3月10日 11:24 | # | 引用

1024bit 說:

引用臘月乘涼的發言:
阮大俠你就別再禍害程序員了,建議先補一下計算機組成原理,編譯原理,算法導論,C語言基礎再來出書,否則你出了也沒人敢看吧。另外起碼要實踐一點兒項目。

var req = new XMLHttpRequest();
req.open('GET', url);
req.onload = function (){};
req.onerror = function (){};
req.send();

var req = new XMLHttpRequest();
req.open('GET', url);
req.send();
req.onload = function (){};
req.onerror = function (){};

你說這倆等價,等價個毛線啊,req.send();以後若是瀏覽器有緩存這個函數會當即返回,也就是說無論你是onload仍是onerror都不會執行。要先綁定回調再去執行send(),這樣纔沒bug。

擦,能不能不要綁定宿主環境。

2015年3月31日 17:16 | # | 引用

簡陋的以數據 說:

感受文章的表述有些亂,好比「不進入主線程、而進入"任務隊列"(task queue)的任務,只有"任務隊列"通知主線程,某個異步任務能夠執行了,該任務纔會進入主線程執行。」,這句讓人理解的意思是「任務隊列」主動通知「主線程」。然後面所表達的倒是「主線程」在執行棧清空後纔會到任務隊列中取任務。先後主被動關係矛盾啊。

還有,「(2)主線程以外,還存在一個"任務隊列"(task queue)。只要異步任務有了運行結果,就在"任務隊列"之中放置一個事件。」。這時異步任務還在隊列裏沒執行呢,怎麼就會有了運行結果呢?

煩請阮老師在方便的時候不吝賜教。同時,感謝阮老師每次耐心細緻的講解和分享!

2015年4月 5日 11:28 | # | 引用

oceanstick 說:

定時器執行流程仍是沒講清楚。。。。

2015年4月 7日 19:49 | # | 引用

timdus 說:

能說說這個網站是怎麼建 的麼

2015年4月 9日 01:44 | # | 引用

雲霄 說:

無論撲靈和你都寫的雲裏霧裏的。。。不透徹。。

2015年7月 6日 20:05 | # | 引用

青苔 說:

老師你好,請教個問題,若是是如圖示Execution Context Stack所展現的那樣,豈不是後來的Context先執行?
那爲什麼
console.log('a');
console.log('b');
能夠獲得咱們所要的執行順序呢?

2015年7月12日 16:28 | # | 引用

andy 說:

上面代碼中,setImmediate和setTimeout被封裝在一個setImmediate裏面,它的運行結果老是1--TIMEOUT FIRED--2,這時函數A必定在timeout前面觸發。
結果不老是1--TIMEOUT FIRED--2 還多是TIMEOUT--1--2。

node 版本v0.12.2 windows 64位系統


2015年8月25日 17:43 | # | 引用

manxisuo 說:

@臘月乘涼:

的確,這個地方阮老師的說法是不對的。二者並不等價。

2015年9月 6日 13:45 | # | 引用

manxisuo 說:

給你們推薦一篇,把Event Loop講得很是清楚的文章:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/EventLoop
中文:
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/EventLoop

2015年9月14日 13:56 | # | 引用

harole 說:

問題圖片丟失。

2015年9月23日 10:20 | # | 引用

shzle 說:

執行棧(execution context stack)清空的時機是什麼?

2015年9月26日 12:30 | # | 引用

kirito 說:

引用簡陋的以數據的發言:
感受文章的表述有些亂,好比「不進入主線程、而進入"任務隊列"(task queue)的任務,只有"任務隊列"通知主線程,某個異步任務能夠執行了,該任務纔會進入主線程執行。」,這句讓人理解的意思是「任務隊列」主動通知「主線程」。然後面所表達的倒是「主線程」在執行棧清空後纔會到任務隊列中取任務。先後主被動關係矛盾啊。

還有,「(2)主線程以外,還存在一個"任務隊列"(task queue)。只要異步任務有了運行結果,就在"任務隊列"之中放置一個事件。」。這時異步任務還在隊列裏沒執行呢,怎麼就會有了運行結果呢?

煩請阮老師在方便的時候不吝賜教。同時,感謝阮老師每次耐心細緻的講解和分享!

同惑啊,「異步任務有了運行結果,就在'任務隊列'之中放置一個事件」,這句不大理解呢,文中不是說同步任務清空以後才執行任務隊列的麼?前面這個異步的結果是哪裏的?求解~~

2015年10月 9日 15:17 | # | 引用

夏爽 說:

window.onload = function(){

document.onmousedown = function(){
var oInput = document.createElement("input");
oInput.value = "輸入內容";
document.body.appendChild(oInput);
oInput.focus();
oInput.select();
}
}

爲何把onmousedowm換成onclick在ie與chrome下顯示效果不一樣

2015年10月17日 21:40 | # | 引用

fans 說:

大神我有個問題,如何判斷主線程結束,能夠進入隊列。何時能第二次進入主線程。

2015年11月 8日 21:44 | # | 引用

alex 傑 說:

技術學術天然有須要嚴謹的地方,老師天然不可能觸類旁引,將各個面都涉及的東西講透,因此有些語言帶過,也有確實考慮不周的地方。一樣是作技術的,我覺有人跟進技術潮流,出來說就很好了,至少人家在治學,勇於發表,聽取意見。

2015年12月 5日 14:40 | # | 引用

Lion 說:

文中提到 「若是將setTimeout()的第二個參數設爲0,就表示當前代碼執行完(執行棧清空)之後,當即執行(0毫秒間隔)指定的回調函數。」,
提到「setImmediate方法則是在當前"任務隊列"的尾部添加事件」,
又提到「Node.js文檔中稱,setImmediate指定的回調函數,老是排在setTimeout前面」。

這三塊東西邏輯矛盾了,確定有誰出錯了,求指點。

2015年12月23日 16:13 | # | 引用

tom 說:

全部windows平臺的軟件都基於消息隊列循環方式。瀏覽器只是普通的windows本地程序,和winform,mfc等都是一樣的機制

2015年12月31日 14:16 | # | 引用

just coding 說:

讀了3遍,仍是有一些不太理解的地方。
好比:同步任務執行完畢以後,纔會去執行異步任務。
那會不會出現這樣一種狀況,a,b,c,d,e是同步任務。1,2,3是異步任務。
當a,b,c,d,e 執行完畢以後,主線程立馬就去將異步任務1,2,3所有讀入執行棧,仍是按順序只讀取第一個?
那若是,a,b,c,d,e執行完畢以後,會不會在某種狀況下,c又要從新執行一次?那此時的c算同步任務仍是異步任務?
還有,到底何種任務叫同步任務,何種任務又要異步任務?
我初淺的理解是:通常瀏覽器的默認執行行爲,好比 dom加載,css渲染之類的都叫同步任務。
click,onload之類的都叫異步任務。
理由是:前者在瀏覽器整個生命週期中,都算一種比較固定的步驟,每一次都要執行。且絕大多數任務都須要等待這些任務執行完畢以後才能執行。也就是說,前者是後者的載體。
異步任務:說實話,我不太理解。比較耗時的任務?不屬於那種生命週期必須執行的任務(好比click)?

因此,當一個瀏覽器打開源代碼給我看,我不太能指出哪些是同步任務,哪一些又是異步任務。

但我獲得的收穫是:javascript是單線程的,開瀏覽器的時候,會有一些默認每次都要執行的代碼屬於主任務,它們放在執行堆棧上,當它們執行完畢以後,主線程會去異步隊列去找那一些準備好了的任務接着執行。前者執行完畢,循環的主要是後者那些異步隊列裏的任務了。
2016年1月 3日 20:26 | # | 引用

k 說:

看得我肚子餓了

2016年3月11日 17:44 | # | 引用

a 說:

這文章誤導人啊

2016年4月13日 22:03 | # | 引用

LeeSir 說:

引用簡陋的以數據的發言:
感受文章的表述有些亂,好比「不進入主線程、而進入"任務隊列"(task queue)的任務,只有"任務隊列"通知主線程,某個異步任務能夠執行了,該任務纔會進入主線程執行。」,這句讓人理解的意思是「任務隊列」主動通知「主線程」。然後面所表達的倒是「主線程」在執行棧清空後纔會到任務隊列中取任務。先後主被動關係矛盾啊。

還有,「(2)主線程以外,還存在一個"任務隊列"(task queue)。只要異步任務有了運行結果,就在"任務隊列"之中放置一個事件。」。這時異步任務還在隊列裏沒執行呢,怎麼就會有了運行結果呢?

煩請阮老師在方便的時候不吝賜教。同時,感謝阮老師每次耐心細緻的講解和分享!

第一個問題阮老師表述可能有問題,可是意思仍是執行完js主線程的代碼纔會去看瀏覽器任務隊列中的事件,再執行js代碼中該事件對應的代碼。
第二個問題個人理解是,任務隊列裏放的是ajax這類的任務,是交給瀏覽器發起HTTP請求去執行的,當有了返回結果就會在任務隊列中增長一個事件表示該ajax請求已經返回告終果,任務隊列裏的任務和js主線程是同時執行的。 不影響js是單線程的這個結論,只能說瀏覽器還會提供接口來供js實現異步操做。

2016年6月28日 12:26 | # | 引用

sweetvvck 說:

引用青苔的發言:
老師你好,請教個問題,若是是如圖示Execution Context Stack所展現的那樣,豈不是後來的Context先執行?
那爲什麼
console.log('a');
console.log('b');
能夠獲得咱們所要的執行順序呢?

Execution Context Stack 存放的是調用關係,你的例子的順序是console.log('a'); 入棧直接運行後出棧,而後console.log('b');入棧,執行後出棧;
若是例子是這樣:

function log() {
console.log(arguments);
}

log('a');

那麼出入棧順序是,log('a'); 入棧,console.log('a');入棧,執行console.log('a');出棧,log('a');出棧;
若是以爲我說的不是很清楚,建議看看這個視頻:http://vimeo.com/96425312

2016年7月25日 21:51 | # | 引用

helloworld 說:

引用LeeSir的發言:
第一個問題阮老師表述可能有問題,可是意思仍是執行完js主線程的代碼纔會去看瀏覽器任務隊列中的事件,再執行js代碼中該事件對應的代碼。
第二個問題個人理解是,任務隊列裏放的是ajax這類的任務,是交給瀏覽器發起HTTP請求去執行的,當有了返回結果就會在任務隊列中增長一個事件表示該ajax請求已經返回告終果,任務隊列裏的任務和js主線程是同時執行的。 不影響js是單線程的這個結論,只能說瀏覽器還會提供接口來供js實現異步操做。

仍是你解釋的比較清楚。

2016年9月12日 17:22 | # | 引用

winder 說:

setImmediate(function (){
setImmediate(function A() {
console.log(1);
setImmediate(function B(){console.log(2);});
});

setTimeout(function timeout() {
console.log('TIMEOUT FIRED');
}, 0);
});

阮老師, 上面的例子, 在測試中也有下面的狀況:
TIMEOUT FIRED
1
2

個人node版本v5.11.0
關於 setTimeout感受它會在插隊"任務隊列"(時間點到了,就優先插隊到任務隊列隊首)

2016年10月18日 10:55 | # | 引用

amber 說:

發現咱們培訓老師講的課應該是借鑑了阮老師的文章。。。有的原話都同樣。。

2016年10月23日 20:06 | # | 引用

叮噹卡丁 說:

「掛起處於等待中的任務,先運行排在後面的任務。等到IO設備返回告終果,再回過頭,把掛起的任務繼續執行下去。」
這句話我有一點不解,這個異步任務是要等全部同步任務都執行完畢了以後進入主線程呢?仍是隻要有IO設備返回結果就立馬進入主線程,能夠插隊到已在主線程的同步任務前面?

2016年11月 4日 17:32 | # | 引用

Allen 說:

自定義的回調函數會放到消息隊列中嗎?

2016年11月11日 10:28 | # | 引用

Jim 說:

關於setImmediate和setTimeout的執行順序,確實是不定的:

setImmediate(function (){
setTimeout(function timeout() {
console.log('同一loop中的setTimeout方法執行,輸出:TIMEOUT FIRED');
}, 0);
console.log('和 setTimeout、setImmediate 同一層次的代碼執行');
setImmediate(function A() {
** console.log('同一loop中的setImmediate 方法執行, 輸出:1' );
setImmediate(function B(){console.log('下一loop中的setImmediate 方法執行, 輸出:2');});
});
});

執行結果:
和 setTimeout、setImmediate 同一層次的代碼執行
同一loop中的setTimeout方法執行,輸出:TIMEOUT FIRED
同一loop中的setImmediate 方法執行, 輸出:1
下一loop中的setImmediate 方法執行, 輸出:2

若是註釋掉標記爲**的那行代碼:
setImmediate(function (){
setTimeout(function timeout() {
console.log('同一loop中的setTimeout方法執行,輸出:TIMEOUT FIRED');
}, 0);
// console.log('和 setTimeout、setImmediate 同一層次的代碼執行');
setImmediate(function A() {
console.log('同一loop中的setImmediate 方法執行, 輸出:1' );
setImmediate(function B(){console.log('下一loop中的setImmediate 方法執行, 輸出:2');});
});
});
執行結果:
同一loop中的setImmediate 方法執行, 輸出:1
同一loop中的setTimeout方法執行,輸出:TIMEOUT FIRED
下一loop中的setImmediate 方法執行, 輸出:2

2016年11月24日 15:50 | # | 引用

Jim 說:

上面的**標記錯了,應該是上邊那句console:

setImmediate(function (){
setTimeout(function timeout() {
console.log('同一loop中的setTimeout方法執行,輸出:TIMEOUT FIRED');
}, 0);
** console.log('和 setTimeout、setImmediate 同一層次的代碼執行');
setImmediate(function A() {
console.log('同一loop中的setImmediate 方法執行, 輸出:1' );
setImmediate(function B(){console.log('下一loop中的setImmediate 方法執行, 輸出:2');});
});
});

2016年11月24日 15:52 | # | 引用

Jim 說:

文章寫得很好,書也寫得好!
http://es6.ruanyifeng.com/#docs/async

2016年11月24日 15:57 | # | 引用

王子龍 說:

setImmediate(function (){
setImmediate(function A() {
console.log('1');
setImmediate(function B(){console.log('2');});
});

setTimeout(function timeout() {
console.log('3');
}, 0);
});

上面這個代碼的輸出順序實際上也是隨機的。多是 1,3,2;也多是3,1,2.

在 I/O 操做的回調裏,他們的順序纔不是隨機的:

fs.readFile('../../README.md', () => {
setTimeout(() => {
console.log('1')
}, 0);

setImmediate(() => {
console.log('2')
});
});

老是先輸出2, 再輸出1. 文檔提到的:
The main advantage to using setImmediate() over setTimeout() is setImmediate() will always be executed before any timers if scheduled within an I/O cycle, independently of how many timers are present.
—— https://github.com/nodejs/node/blob/master/doc/topics/event-loop-timers-and-nexttick.md

2016年12月30日 15:02 | # | 引用

losymear 說:

呀 樸靈老師批註找不到了

2016年12月30日 16:37 | # | 引用

kevin 說:

若是執行棧(主線程)在執行完正要loop任務隊列前,主線程的代碼又被觸發執行了一次會怎麼樣?好比兩次點擊,是會先執行任務隊列仍是主線程的代碼

2017年1月 9日 14:56 | # | 引用

和中堂 說:

阮老師是一位不錯的踐行者,我前端典範。

2017年1月18日 14:11 | # | 引用

goodyboy6 說:

引用fans的發言:
大神我有個問題,如何判斷主線程結束,能夠進入隊列。何時能第二次進入主線程。

`event queue`中新增長event(好比點擊) 和 `任務隊列`中任務完成觸發回調都會再次進入主線程。
2017年2月 7日 10:59 | # | 引用

goodyboy6 說:

根據MDN上官方的描述:
> The event loop got its name because of how it's usually implemented, which usually resembles:

while (queue.waitForMessage()) {
queue.processNextMessage();
}

queue.waitForMessage waits synchronously for a message to arrive if there is none currently.

這篇文章中大量的關於主線程`主動獲取任務事件結束回調`的描述是錯誤的。回調會讓`queue.waitForMessage()`返回`true`,激活新的event loop,從而及時處理回調。

2017年2月 7日 11:38 | # | 引用

hank 說:

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

阮老師,我測試的這個運行結果。也是不肯定的,還有多是TIMEOUT FIRED--1--2

2017年2月19日 17:43 | # | 引用

yyf 說:

一直有一個疑問未能解開,Event Loop這個機制是否是由一個單獨的(瀏覽器/node)線程控制,好比setTimeout(()=>{}, 1000)方法,延遲1000ms,是誰來監控這1000ms後加入消息queue的,是瀏覽器的某個(事件處理)線程麼?

2017年2月27日 12:15 | # | 引用

楊守科 說:

引用yyf的發言:
一直有一個疑問未能解開,Event Loop這個機制是否是由一個單獨的(瀏覽器/node)線程控制,好比setTimeout(()=>{}, 1000)方法,延遲1000ms,是誰來監控這1000ms後加入消息queue的,是瀏覽器的某個(事件處理)線程麼?

對的 以前我也很疑惑 若是是單線程只能作一件事 執行到setTimeout 或ajax以後 把他們放入"事件池" 再去執行後續的代碼 這個時候就會出現問題 說好的單線程只能作一件事的引擎怎麼一邊監控事件池的狀態一邊執行後續的代碼呢? 後來偶然看到一位大牛的博客翻譯了一篇牆外的文章才忽然頓悟(文章地址找不到了)。由於瀏覽器是多線程啊 當瀏覽器監控到"事件池"狀態更新時會通知改變js引擎 這時候js引擎會在空閒的時候停下來去執行"事件池"裏面的回調 所謂空閒的時候是指的是當前引擎執行的語句塊上下文執行完畢時的正要執行下一個語句塊時的狀態 好比你在 setTimeout(()=>{console.log(1000 end)}, 1000) 後面寫個for的死循環 由於單線程 你永遠也沒法獲得輸出 或是在setTimeout後面寫一個函數讓其循環1300後它就會在1300後去回調輸出而不是1000後

2017年3月19日 16:13 | # | 引用

CRONWMMM 說:

做爲NodeJS的初學者,這是我看到過的最好的NodeJS事件循環機制的解析教程,終於弄懂了,謝謝老師!

2017年3月29日 13:40 | # | 引用

s 說:

好像挺簡單的, 爲何搞得好像很深奧的樣子呢

2017年4月12日 15:33 | # | 引用

tiny 說:

文章中的這段話:可是,因爲存在後文提到的"定時器"功能,主線程首先要檢查一下執行時間,某些事件只有到了規定的時間,才能返回主線程。

引發了我很大的疑惑,請樓主參考一下
http://ejohn.org/blog/how-javascript-timers-work/

2017年4月21日 11:10 | # | 引用

哈哈 說:

真心搞不懂了。。回調那個對嗎?

2017年4月27日 14:32 | # | 引用

瑞銘 說:

> 使人困惑的是,Node.js文檔中稱,setImmediate指定的回調函數,老是排在setTimeout前面。實際上,這種狀況只發生在遞歸調用的時候。

實際上, 應該是隻發生在異步 I/O 回調中. 這句話下面給的例子, 我在本地測試輸出是有兩種可能結果: TIMEOUT FIRED - 1 - 2 和 1 - TIMEOUT FIRED - 2.

若是是下面的例子

```js
const fs = require('fs')
fs.readdir(__dirname, () => {
setImmediate(() => console.log(1))
setTimeout(() => console.log(2), 0)
})
setImmediate(() => console.log('A'))
setTimeout(() => console.log('A'), 0) // setTimeout 和 setImmediate 結果可對換, 所以都輸出 A 就好
```

上面例子結果只有兩種可能, 一種是當異步回調在當前事件循環被執行時結果爲 A 1 A 2. 一種是異步回調在下一個事件循環被執行, 結果是 A A 1 2. 1 永遠先於 2 輸出. 2 永遠在最後輸出. 屢試不爽.

2017年6月10日 21:55 | # | 引用

邢坤 說:

所謂"回調函數"(callback),就是那些會被主線程掛起來的代碼。異步任務必須指定回調函數,當主線程開始執行異步任務,就是執行對應的回調函數。
這句話說錯了吧?
function a(callback){
callback();
while(true){

}
}
a(function(){
alert("觸發了,沒有進入異步隊列")
});
能不能解釋一下您說的回調進入異步隊列,爲何alert仍是會執行?

2017年6月29日 15:30 | # | 引用

light 說:

@邢坤:

。。。叫callback就是回調函數了?

2017年7月 2日 16:22 | # | 引用

素彌 說:

所謂"回調函數"(callback),就是那些會被主線程掛起來的代碼。
我的感受這裏的說法不靠譜,我的理解的回調函數就是在一個函數中調用另一個函數,只是說異步裏使用回調函數的場景不少,但並不能這樣("所謂"回調函數"(callback),就是那些會被主線程掛起來的代碼")來解釋回調函數吧?

2017年7月10日 10:32 | # | 引用

薛皓月 說:

任務隊列裏面放的應該是回調函數,而非事件吧?那上面寫的是callback queue,並且邏輯上這樣也更通順吧?

2017年7月12日 22:15 | # | 引用

kaimi 說:

彷佛仍是有問題啊,例子2執行和推理矛盾

2017年8月 9日 11:18 | # | 引用

Lemon 說:

HTML中的自定義事件和Node中的自定義事件是不是異步的?

2017年8月24日 17:53 | # | 引用

wuliao 說:

引用Hiccup的發言:
不正確的文章爲何不刪掉

確實啊,不刪掉就算了,也不在以前的文章標個大標頭提示一下。看了以前那篇,結果再看這篇,開頭就是說以前那篇理解有誤。能不讓人說什麼呢?感受這位博主寫的不少不嚴謹,好多東西都只是引出處再略微表示一下本身的理解,都沒有去驗證的。不知道爲啥名氣這麼高哦???之後不敢看此人的博客了,生命有限...

2017年9月25日 12:28 | # | 引用

殷敏峯 說:

有相似動態動畫描述下 Node 的兩個方法?

2017年10月12日 17:19 | # | 引用

aaa 說:

引用wuliao的發言:
確實啊,不刪掉就算了,也不在以前的文章標個大標頭提示一下。看了以前那篇,結果再看這篇,開頭就是說以前那篇理解有誤。能不讓人說什麼呢?感受這位博主寫的不少不嚴謹,好多東西都只是引出處再略微表示一下本身的理解,都沒有去驗證的。不知道爲啥名氣這麼高哦???之後不敢看此人的博客了,生命有限...

對於咱們這些小白來講,不看阮老師的文章會走更多錯誤的路,看了阮老師的文章,少走不少彎路。
並且,阮老師的文章 通俗易懂,有錯誤也正常啊。
你本身理解,估計連犯錯誤的機會都沒有吧。能看懂嗎?就你這智商?

牛人多了,能讓咱們這些小白受益的又有幾個?
天下之大,免費的有幾樣?除了陽光?空氣?還有什麼?
若是你以爲本身很是聰明,那能夠不看對吧?
2017年11月 9日 11:17 | # | 引用

Jeff 說:

引用wuliao的發言:
確實啊,不刪掉就算了,也不在以前的文章標個大標頭提示一下。看了以前那篇,結果再看這篇,開頭就是說以前那篇理解有誤。能不讓人說什麼呢?感受這位博主寫的不少不嚴謹,好多東西都只是引出處再略微表示一下本身的理解,都沒有去驗證的。不知道爲啥名氣這麼高哦???之後不敢看此人的博客了,生命有限...

其實一直以來,阮老師就代表他寫的文章只是本身的認識罷了,沒有強迫任何一我的認爲他如何如何,也沒有由於博客的事情和別人撕逼。他只是安安靜靜本身學習而已。名氣大與不大並非他本身的緣由,更不存在故意惡意引導。 不喜歡能夠不看,要加強自我辨識能力纔是最根本的

2017年11月14日 10:17 | # | 引用

我要發表見解
您的留言 (HTML標籤部分可用)

 

您的大名:


«-必填

電子郵件:


«-必填,不公開

我的網址:


«-我信任你,不會填寫廣告連接

記住我的信息?

發表 «- 點擊按鈕

微博 | 推特 | GitHub2017 © 個人郵件 |

相關文章
相關標籤/搜索