首先來一段代碼開篇javascript
console.log(1);
setTimeout(function() {
console.log(2);
});
function fn() {
console.log(3);
setTimeout(function() {
console.log(4);
}, 2000);
}
new Promise(function(resolve, reject){
console.log(5);
resolve();
console.log(6);
}).then(function() {
console.log(7);
})
fn();
console.log(8);
複製代碼
思考一下,能給出準確的輸出順序嗎?java
下面一步步的瞭解,最後看看這塊代碼怎麼去執行的。node
進程: 運行的程序就是一個進程,好比你正在運行的瀏覽器,它會有一個進程。git
線程: 程序中獨立運行的代碼段。github
一個進程由單個或多個線程組成,線程是負責執行代碼的。ajax
學過JS的想必都知道JS是單線程的,那麼既然有單線程就有多線程,下面首先看看單線程與多線程的區別。api
單線程 從頭執行到尾,一行一行執行,若是其中一行代碼報錯,那麼剩下代碼將再也不執行。同時容易代碼阻塞。promise
多線程 代碼運行的環境不一樣,各線程獨立,互不影響,避免阻塞。瀏覽器
js既然是單線程,那麼確定是排隊執行代碼,那麼怎麼去排這個隊,就是Event Loop。雖然JS是單線程,但瀏覽器不是單線程。瀏覽器中分爲如下幾個線程:bash
其中JS線程和UI線程相互互斥,也就是說,當UI線程在渲染的時候,JS線程會掛起,等待UI線程完成,再執行JS線程
JS會存在執行棧,從上至下執行js代碼,當遇到異步api時,列如上面所述的各類非JS線程的事件,那麼會扔給對應的線程去處理,等處理完畢後,則把回調函數放入事件隊列中,等待執行棧執行完畢,再去讀取事件隊列中的回調函數執行。
整個過程,執行棧,讀取事件隊列就是Event Loop
再來看看promise, 若是對promise不是很瞭解的同窗能夠看看另外一篇我寫的文章Promise是個什麼鬼?實現一個Promise.
Promise在整個執行中是個特殊的存在,傳入Promise的fn是在當前執行棧中的,會當即執行,但它的then方法是在執行棧以後,事件隊列以前,固然這個和瀏覽器實現有關,大部分瀏覽器是微任務(Microtask),也有瀏覽器放入了宏任務(Macrotask),chorme大哥是放入了微任務,其餘紛紛效仿。那你們可能會問什麼是微任務?什麼是宏任務了?
Nodejs是經過V8引擎去解析的,解析後的代碼會去調用node提供的api執行,這些API由libuv這個庫去分配線程執行,最後異步返回給V8引擎。
在Node中提供了2個方法和咱們的執行隊列有關
把方法放入執行棧的底部,並不放入宏任務和微任務
cosnole.log(1);
process.nextTick(function(){
console.log(2);
});
new Promise(function() {
console.log(3);
}).then(function() {
console.log(4);
})
console.log(5);
複製代碼
由於nextTick是放入了執行棧的底部,那麼會優先於Promise的then方法,故輸出爲1 3 5 2 4
把方法放入宏任務的隊列中去,但有一個奇怪的事發生,看下面代碼:
setImmediate(function() {
console,log(1);
});
setTimeout(function() {
console.log(2);
}, 0);
複製代碼
你們能夠試試把代碼屢次執行,發現輸出順序不必定,他們都是放入了宏任務中,但在node文檔中,setImmediate老是排在setTimeout前面,可是在實際中確不必定,不知道是否是一個bug。
setTimeout(function(){
console.log(1);
}, 2000);
task();
複製代碼
假設task函數執行須要5秒鐘,那麼打印1須要在5秒以後再打印,task佔用了當前執行棧,要等執行棧執行完畢後再去讀取微任務,等微任務完成,這個時候纔會去讀取宏任務裏面的setTimeout回調函數執行。setInterval同理,例如每3秒放入宏任務,也要等到執行棧的完成。
setTimeout(function() {
console.log(1);
},0);
複製代碼
可是根據標準這個時候最低是4毫秒,即使如今執行棧已經完成。0是不成立的。寫0瀏覽器爲默認爲最低毫秒數。
如今再回到上面的代碼,有答案了嗎?
// 非異步api,當即執行
console.log(1);
// 放入全局宏任務
setTimeout(function() {
console.log(2);
});
// 聲明函數,但暫時未調用,不會立馬造成執行棧
function fn() {
// 調用fn時當即執行
console.log(3);
// 放入當前fn執行棧宏任務
setTimeout(function() {
console.log(4);
}, 2000);
}
new Promise(function(resolve, reject){
// task任務當即執行
console.log(5);
resolve();
console.log(6);
}).then(function() {
// then方法放入微任務
console.log(7);
})
// 調用fn進入下個執行棧
fn();
// fn執行棧完成執行
console.log(8);
複製代碼
答案就是 1 5 6 3 8 7 2 4