宿主環境javascript
window
、document
、setTimeout
、脫離瀏覽器就不會有這些API。瀏覽器中的功能模塊:一個模塊佔用一個線程。java
> JS是單線程的緣由是瀏覽器只會開一個JS執行引擎
setTimeout
,promise
等能夠調用其它線程的API才能執行異步代碼。用於存放執行棧清空後執行的異步代碼,一般從計時器、網絡請求、事件監聽等其它線程中獲取。node
setTimeout
等函數時,會調用宿主中的其它線程(計時器);圖解:
segmentfault
舉個例子:這裏以setTimeout
爲例,其它異步方法都是同樣的promise
setTimeout(function bar(){ console.log('計時器回調執行'); }, 0) function foo(){ console.log('foo函數執行'); } foo();
//執行過程僞代碼: 1. 執行棧入棧setTimeout函數; 執行棧:[setTimeout] 計時線程:[] 事件隊列:[] 2. 瀏覽器遇到其它線程API,通知對應線程執行代碼,此時瀏覽器喚起的是計時器線程,將setTimeout方法推入計時器線程; 執行棧:[] 計時線程:[setTimeout] 事件隊列:[] 3. 執行棧繼續執行後續代碼,入棧foo函數,同時,計時線程中的setTimeout在計時; 執行棧:[foo] 計時線程:[setTimeout] 事件隊列:[] 4. 計時完畢,計時線程將setTime方法的回調函數推入事件隊列,而後計時器線程中的setTimeout執行完畢被銷燬; 執行棧:[foo] 計時線程:[] 事件隊列:[bar] 5. 執行foo函數,打印'foo函數執行',foo執行完畢,foo函數出棧; 執行棧:[] 計時線程:[] 事件隊列:[bar] 6. 執行棧沒有可執行代碼,會隔一段時間去事件隊列中查看(事件輪詢)一下有沒有可執行代碼,此時查看到有一個bar函數,將bar函數推入執行棧; 執行棧:[bar] 計時線程:[] 事件隊列:[] 7. 執行bar函數,打印'計時器回調執行',bar執行完畢,bar函數出棧; 執行棧:[] 計時線程:[] 事件隊列:[]
從上述例子能夠看出:瀏覽器
- setTimeout的計時並不許確,計時完成後只是將回調函數推入事件隊列,並非立刻執行,還要等執行棧清空後纔會執行;
- 無論setTimeout的方法是否是寫在最前面,計時時間有多短,都是在同步代碼執行完畢後纔會執行。
事件隊列又能夠細分爲宏隊列和微隊列,每次執行棧清空後會先執行微隊列中的任務,再執行宏隊列中的任務;微信
Promise.then
、mutationObserver
舉個例子:網絡
setTimeout(function(){ console.log('計時完成'); //3. 最後執行宏任務中的隊列 }, 0); new Promise(function(resolve, reject){ console.log('promise 代碼執行'); //1. 先執行,這裏的代碼是同步的 resolve(); }).then(function(){ console.log('promise resolve'); //2. 執行棧空了以後,先執行微隊列中的任務 })
無論宏任務中的代碼寫在哪裏,都是在微任務執行後再執行。由於瀏覽器會在微隊列清空後再去看宏隊列。