主線程運行的時候,產生堆和棧(堆heap棧stack)
棧中的代碼調用各類外部API,他們在「任務隊列」中加入各類事件(click,load,done)。只要棧中的代碼執行完畢,主線程就會去讀取「任務隊列」,依次執行那些事件所對應的回調函數。
執行棧中的代碼(同步任務),老是在讀取「任務隊列」(異步任務)以前執行。
eg:瀏覽器
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(); req.send(); req.onload = function(){}; req.onerror = function(){};
也就是說,指定回調函數的部分 onload和onerror,在send()方法的前面或者後面可有可無,由於他們屬於執行棧的一部分,系統老是執行完他們,纔會去讀取「任務隊列」。異步
定時器
出了放置異步任務的事件,「任務隊列」還能夠放置定時事件,即 指定某些代碼在多少時間以後執行。這叫作「定時器」功能,也就是定時執行的代碼。
定時器功能主要由setTimeout()和setInterval()這兩個函數來完成,他們的內部運行機制徹底同樣,區別在於前者指定的代碼是一次性執行,後者則爲反覆執行。
setTimeout()接受兩個參數,一個是回調參數,第二個是推遲執行的毫秒數。函數
console.log(1); setTimeout(function(console.log(2))}, 1000); console.log(3);
上面代碼執行的結果是1,3,2。由於setTimeout()將第二行推遲到1000毫秒以後執行。
若是將setTimeout()第二個參數設爲0,就表示當前代碼執行完之後,也就是執行棧清空後,纔會當即執行(0毫秒間隔)指定的回調函數。oop
setTimeout(function(){console.log(1);}, 0); console.log(2);
上面代碼的執行結果是2,1。由於只有在執行完第二行之後,系統纔會去執行「任務隊列」中的回調函數,也就是setTimeout()。
總之,setTimeout(fn,o)的含義是,指定某個任務在主線程最先可得的空閒時間執行,也就是說,儘量早的執行。他在「任務隊列」的尾部添加一個事件,所以要等到同步任務和「任務隊列」現有的事件都處理完,纔會獲得執行。
HTML5標準規定了setTimeout()的第二個參數的最小值(最短間隔),不得低於4毫秒,若是低於這個值,就會自動增長。在此以前,老版本的瀏覽器都將最短間隔設爲10毫秒。另外,對於那些DOM的變更(尤爲是涉及頁面從新渲染的部分),一般不會當即執行,而是每16毫秒執行一次。這時使用requestAnimationFrame()的效果漢語 setTimeout().url
須要注意的是,setTimeout()只是將事件插入了「任務隊列」,必須等到當前代碼執行完,(執行棧清空)。主線程纔會去執行他指定的回調函數。要是當前代碼耗時很長,有可能要等好久,因此並無辦法保證,回調函數必定會在setTimeout()指定的時間執行。spa
Node.js的Event Loop
Node.js也是單線程的EventLoop,可是他的運行機制不一樣於瀏覽器環境。線程
Node.js的運行機制以下code
1.V8引擎解析JavaScript腳本。
2.解析後的代碼,調用NodeAPI。
3.libuv庫複雜Node API的執行。他將不一樣的任務分配給不一樣的線程,造成一個Event Loop(事件循環),以異步的方式將任務的執行結果返回給V8引擎。
4.V8引擎再將結果返回給用戶。blog
除了setTimeout和setInterval這兩個方法,Node.js還提供了另外兩個與「任務隊列」有關的方法:
process.nextTick和setImmediate。
他們能夠幫助咱們加深對「任務隊列」的理解。
process.nextTick方法能夠在當前「執行棧」的尾部---下一次EventLoop(主線程讀取「任務隊列」)以前,觸發回調函數。也就是說,他指定的任務老是發生在全部異步任務以前。
setImmediate方法則是在當前「任務隊列」的尾部添加事件,也就是說,他指定的任務老是在下一次EventLoop時執行,這與setTimeout(fn,o)很像。
e.g
process.nextTick(function A(){ console.log(1); process.nextTick(function B(){ console.log(2); }) }); setTimeout(function timeout(){ console.log('timeout'); }, 0)
執行結果:1,2,timeout
上面代碼中,因爲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'); }, 0)
上面代碼中,setImmediate與setTimeout(fn,o)各自添加了一個回調函數A和timeout,都是在下一次EvebtLoop觸發。那麼 運行的結果是不肯定的。
多是1,timeout,2也有多是timeout,1,2可是在Node.js文檔中說,setImmediate指定的回調函數,老是排在setTimeout前面。