看了阮一峯老師的JavaScript 運行機制詳解:再談Event Loop和【樸靈評註】的文章,查閱網上相關資料,把本身對javascript運行模式和EVENT loop的理解整理下,不必定對,往後再看作個回顧。javascript
MDN上有張圖很形象,
html
function f(b){
var a = 12;
return a+b+35;
}
function g(x){
var m = 4;
return f(m*x);
}
g(21);
上面函數調g用造成了一個 frames 的棧。調用g的時候,建立了第一個 frame,包含了 g 的參數和局部變量。當 g 調用 f 的時候,第二個 frame 就被建立、並置於第一個 frame 之上,包含了 f 的參數和局部變量。當f返回時,最上層的 frame 就出棧了(剩下 g 函數調用的 frame)。當g返回的時候,棧就空了。java
隊列
一個 JavaScript 運行時包含了一個待處理的消息隊列。每個消息都與一個函數相關聯。當棧爲空時,從隊列中取出一個消息進行處理。這個處理過程包含了調用與這個消息相關聯的函數(以及所以而建立的一個初始棧結構)。當棧再次爲空的時候,也就意味着消息處理結束。 node
在瀏覽器裏,當一個事件出現且有一個事件監聽器被綁定時,消息會被隨時添加。若是沒有事件監聽器,事件會丟失。因此點擊一個附帶點擊事件處理函數的元素會添加一個消息。其它事件亦然。ajax
毫不阻塞
一個頗有趣的事件循環 (event loop) 模型特性在於,Javascript 跟其它語言不一樣,它永不阻塞。處理 I/O (input/output) 一般由事件或者回調函數進行實現。因此當一個應用正等待 IndexedDB 的查詢的返回或者一個 XHR 的請求返回時,它仍然能夠處理其它事情例如用戶輸入。瀏覽器
例外是存在的,如 alert 或者同步 XHR,但避免它們被認爲是最佳實踐。注意的是,例外的例外也是存在的(但一般是實現錯誤而非其它緣由)。markdown
Event Loop
舉例node.js的Event Loop
異步
樸靈的解釋函數
使用事件驅動的系統中,必然有很是很是多的事件。若是事件都產生,都要主循環去處理,必然會致使主線程繁忙。那對於應用層的代碼而言,確定有不少不關心的事件(好比只關心點擊事件,不關心定時器事件)。這會致使必定浪費。oop
【事實上,不是全部的事件都放置在一個隊列裏。】
【不一樣的事件,放置在不一樣的隊列。】
【當咱們沒有使用定時器時,則徹底不用關心定時器事件這個隊列】
【當咱們進行定時器調用時,首先會設置一個定時器watcher。事件循環的過程當中,會去調用該 watcher,檢查它的事件隊列上是否產生事件(比對時間的方式)】
【當咱們進行磁盤IO的時候,則首先設置一個io watcher,磁盤IO完成後,會在該io watcher的事件隊列上添加一個事件。事件循環的過程當中從該watcher上處理事件。處理完已有的事件後,處理下一個watcher】
【檢查完全部watcher後,進入下一輪檢查】
【對某類事件不關心時,則沒有相關watcher】
定時器
定時器僅僅是在將來的某個時刻將代碼添加到代碼隊列中,執行時機是不能保證的。代碼隊列按照先進先出的原則在主進程空閒後將隊列中的代碼交給主線程運行。
在Javascript中沒有任何代碼是馬上執行的,帶一旦進程空閒則儘快執行。例如,當某個按鈕被按下時,事件處理函數會被添加到代碼隊列中。當接收到ajax響應時,回校函數的代碼被添加到隊列中。而定時器對隊列的工做方式是,當特定的事件過去後將代碼加入到隊列中。設定一個150ms後執行的定時器不表明代碼會在150ms以後執行,而是指代碼會在150ms後加入到代碼隊列中。等到主進程空閒時而且該元素位於隊列首位,其中的代碼便會當即執行,看上去好像是在精確的時間點上執行了。實際上隊列中的全部代碼都要等到主進程空閒以後才能執行,而無論他們是怎額添加到隊列中去的。
(function () { console.log('this is the start'); setTimeout(function cb() { console.log('this is a msg from call back'); }); console.log('this is just a message'); setTimeout(function cb1() { console.log('this is a msg from call back1'); }, 0); console.log('this is the end'); var time= new Date(); while(new Date() - time < 2000) {} })();
//打印順序以下:
this is the start
this is just a message
this is the end
//2S以後執行settimeout內容,雖然0秒後執行setTimeout內容,可是主線程代碼還沒執行完,so等主線程空閒的時候再當即執行setTimeout代碼
this is a msg from call back
this is a msg from call back1
當使用setInterval()時,僅當沒有該定時器的任何其餘代碼實例時,才能將定時器代碼添加到代碼隊列中。
var original = Date.now();
setInterval(function(){
console.log('run interval', Date.now() - original);
var start = Date.now();
while(Date.now() - start < 350) {};
original = Date.now();
}, 200);
var start = Date.now();
while(Date.now() - start < 300) {};
在使用setInterval時:
因此在使用setInterval作動畫時要注意兩個問題:
不能使用固定步長做爲作動畫,必定要使用百分比: 開始值 + (目標值 - 開始值) * (Date.now() - 開始時間)/ 時間區間
若是主進程運行時間過長,會出現跳幀的現象。爲了不setInterval的兩個缺點,可使用鏈式setTimeout():
setTimeout(function(){
//其餘處理
setTimeout(arguments.callee, interval);
}, interval);