javascript是單線程,一切javascript版的"多線程"都是用單線程模擬出來的,經過事件循環(event loop)實現的異步。javascript
事件循環中的同步任務,異步任務:html
同步和異步任務在不一樣的執行"場所",同步的進入主線程,異步的進入Event Table執行並註冊函數。前端
當指定的異步事情完成時,Event Table會將這個函數移入Event Queue。java
主線程內的任務執行完畢爲空,會去Event Queue讀取對應的函數,推入主線程執行。node
js引擎的monitoring process進程會持續不斷的檢查主線程執行棧是否爲空,一旦爲空,就會去Event Queue那裏檢查是否有等待被調用的函數。上述過程會不斷重複,也就是常說的Event Loop(事件循環)。ajax
用個例子說明上述過程:segmentfault
let data = []; $.ajax({ url:www.javascript.com, data:data, success:() => { console.log('發送成功!'); } }) console.log('代碼執行結束');
ajax(異步任務)進入Event Table,註冊回調函數success。promise
執行console.log('代碼執行結束')。(同步任務在主線程執行)瀏覽器
ajax事件完成,回調函數success進入Event Queue。多線程
主線程從Event Queue讀取回調函數success並執行。
console.log('先執行這裏'); setTimeout(() => { console.log('執行啦') },3000);
//先執行這裏
// ... 3s later 3秒到了,計時事件timeout完成,定時器回調進入Event Queue,主線程執行已執行完,此時執行定時器回調。
// 執行啦
setTimeout明明寫的延時3秒,實際卻5,6秒才執行函數,這咋回事?
setTimeout(() => { task() },3000) sleep(10000000)
上述代碼在控制檯執行task()須要的時間遠遠超過3秒,執行過程:
task()進入Event Table並註冊,計時開始。
執行sleep函數,很慢,很是慢,計時仍在繼續。
3秒到了,計時事件timeout完成,task()進入Event Queue,可是sleep也太慢了吧,還沒執行完,只好等着。
sleep終於執行完了,task()終於從Event Queue進入了主線程執行。
上述的流程走完,setTimeout這個函數是通過指定時間後,把要執行的任務(本例中爲task())加入到Event Queue中,又由於是單線程任務要一個一個執行,若是前面的任務須要的時間過久,那麼只能等着,致使真正的延遲時間遠遠大於3秒。
重點來了:定時器的毫秒,不是指過ms秒執行一次fn,而是過ms秒,會有fn進入Event Queue。
setTimeout(fn,0)的含義是,指定某個任務在主線程最先可得的空閒時間執行,意思就是不用再等多少秒了,只要主線程執行棧內的同步任務所有執行完成,棧爲空就立刻執行。
關於setTimeout要補充的是,即使主線程爲空,0毫秒實際上也是達不到的。根據HTML的標準,最低是4毫秒。
對於執行順序來講,setInterval會每隔指定的時間將註冊的函數置入Event Queue,若是前面的任務耗時過久,那麼一樣須要等待。一旦setInterval的回調函數fn執行時間超過了延遲時間ms,那麼就徹底看不出來有時間間隔了。
除了廣義的同步任務和異步任務,咱們對任務有更精細的定義:
macro-task(宏任務):包括總體代碼script,setTimeout,setInterval
micro-task(微任務):Promise,process.nextTick
不一樣類型的任務會進入對應的Event Queue,好比setTimeout和setInterval會進入相同的Event Queue。
在宏任務和微任務概念中的事件循環機制:
主任務(宏任務)完——全部微任務——宏任務(找到宏任務其中一個任務隊列執行,其中若是又有微任務,該任務隊列執行完就執行微任務)——宏任務中另一個任務隊列(裏面偶微任務就再執行微任務)。
總的來講就是在宏任務和微任務之間來回切。下面列子執行過程:
第一輪:主線程輸出:【1,7】,添加宏任務【set1,set2】,添加微任務【6,8】。執行完主線程,而後執行微任務輸出【6,8】
第二輪:執行宏任務其中一個任務隊列set1:輸出【2,4】,執行任務的過程,碰到有微任務,因此在微任務隊列添加輸出【3,5】的微任務,在set1宏任務執行完就執行該微任務,第二輪總輸出:【2,4,3,5】
第三輪:執行任務另外一個任務隊列set2:輸出【9,11】,執行任務的過程,碰到有微任任務,因此在微任務隊列添加輸出【10,12】的微任務,在set2宏任務執行完就執行該微任務,第三輪總輸出:【9,11,10,12】
整段代碼,共進行了三次事件循環,完整的輸出爲1,7,6,8,2,4,3,5,9,11,10,12。(請注意,node環境下的事件監聽依賴libuv與前端環境不徹底相同,輸出順序可能會有偏差)
console.log('1'); //第一輪主線程【1】
setTimeout(function() { //碰到set異步,丟入宏任務隊列【set1】:我將它命名爲set1
console.log('2');//第二輪宏任務執行,輸出【2】
process.nextTick(function() {//第二輪宏任務執行,碰到process,丟入微任務隊列,【3】
console.log('3');
})
new Promise(function(resolve) {//第二輪宏任務執行,輸出【2,4】
console.log('4');
resolve();
}).then(function() {
console.log('5')//第二輪宏任務執行,碰到then丟入微任務隊列,【3,5】
})
})
process.nextTick(function() { //碰到process,丟入微任務隊列【6】
console.log('6'); //第一輪微任務執行
})
new Promise(function(resolve) {
console.log('7'); //new的同時執行代碼,第一輪主線程此時輸出【1,7】
resolve();
}).then(function() {
console.log('8') //第一輪主線程中promise的then丟入微任務隊列,此時微任務隊列爲【6,8】。當第一輪微任務執行,順序輸出【6,8】
})
setTimeout(function() { //碰到set異步丟入宏任務隊列,此時宏任務隊列【set1.set2】:我將它命名爲set2
console.log('9');//第三輪宏任務執行,輸出【9】
process.nextTick(function() { //第三輪宏中執行過程當中添加到微任務【10】
console.log('10');
})
new Promise(function(resolve) {
console.log('11');//第三輪宏任務執行,宏任務累計輸出【9,11】
resolve();
}).then(function() {
console.log('12') //第三輪宏中執行過程當中添加到微任務【10,12】
})
})
參考文章: