面試時你是否會遇到下面的問題?web
console.log(1)
setTimeout(function(){
console.log(2)
},1000)
new Promise(function(resolve,rejected){
console.log(3)
resolve('同步任務結果')
}).then(res=>{
console.log('4',res)
})
console.log(5)
複製代碼
其實這些問題的核心都是一個:事件循環,那事件循環究竟是什麼呢?面試
那下面就讓咱們來一塊兒學起來吧💃💃ajax
你們都知道js是個單線程,單線程就意味着任務要排隊,那麼像setTimeout,ajax這種任務,若是也要排隊,就會形成等待耗時,長時間等待若是無響應,就會形成頁面空白,用戶體驗差。因此就出現了同步異步任務,同步任務在主線程上執行,異步任務在旁邊執行,執行後把結果放在任務隊列中,等到同步任務全都執行完畢後,就去任務隊列中取異步任務的結果,而後按順序執行。異步任務隊列中還分爲微任務和宏任務,微任務先與宏任務執行。如此反覆的一個過程就叫事件循環,也就是js的異步執行機制promise
下面來逐條分析瀏覽器
爲何是單線程呢?由於js多用來處理dom操做,若是是多線程,2個線程分別對dom作操做, 瀏覽器不能知道以哪一個線程爲主,因此js必須是單線程bash
瀏覽器雖然是多線程的,可是隻分配了一個主線程給js執行任務,並且一次只能執行一個任務, 可是js中不少的好比
網絡請求(http請求的鏈接須要tcp三次握手創建鏈接,有等待時間)
定時器(延時操做)
事件監聽(onclick事件)
等會很是的耗時,致使執行效率低下,甚至頁面假死網絡
異步叫任務隊列
)整個程序是事件驅動的,每一個事件都會綁定相應的回調函數,任務隊列中都是已經完成的異步操做, 而不是你註冊的異步事件
注意:不管異步操做什麼時候開始執行,只要執行完畢就放在消息隊列中,也就是說放在隊列中的是回調函數的結果
多線程
Vue 中對於宏任務的實現:dom
- 優先檢測是否支持原生的setImmediate(這是一個高版本IE和Edge才支持的特性)
- 再去檢測是否原生支持MessageChannel
- 以上都不支持, 就降級爲setTimeout 0
他們的執行順序是:先執行全部的微任務,再執行下一個宏任務 異步
全部進入異步的都是事件回調的那部分代碼,因此
new promise實例化過程當中的代碼是同步的,then以後執行的纔是微任務,微任務會在宏任務以前執行
await 是基於promise封裝的,因此await以前的是同步代碼,以後的是異步
事件循環機制
看完上面應該就知道該如何回答面試官的問題了吧!
1 3 5 是同步任務。按順序執行。
4 是微任務。
2 是宏任務。
在上圖中,調用棧中遇到DOM操做、ajax請求以及setTimeout等WebAPIs的時候就會交給瀏覽器內核的其餘模塊進行處理,webkit內核在Javasctipt執行引擎以外,有一個重要的模塊是webcore模塊。.對於圖中WebAPIs提到的三種API,webcore分別提供了DOM Binding、network、timer模塊來處理底層實現。等到這些模塊處理完這些操做的時候將回調函數放入任務隊列中,以後等棧中的task執行完以後再去執行任務隊列之中的回調函數。 ![]()
既然已經說到這裏了, 順便說下有關於setTimeout的應用的面試題
<!--簡單版本-->
var timer ;
function debounce(){
if(timer){
clearTimeout(timer)
}
timer = setTimeout(()=>{
console.log("一秒後發請求")
},1000)
}
debounce();
複製代碼
<!--簡單版本-->
var flag = true;
function throttle() {
if (!flag) return false;
flag = false;
setTimeout(() => {
console.log('2秒內如論調用多少次此函數,都只會打印一次結果')
flag = true;
}, 2000)
}
throttle();
複製代碼
setTimout 延遲操做,setInterval是按指定週期調用函數 因此核心應該就是,
若是timeid寫在了函數裏,那麼你去控制檯調用一下代碼會發現,最終return的timeid是210 ,可是每次遞歸調用的setTimeout的id卻都是不同的,然而最後return出來的並非最新的id,那麼你去清除210的時候就發現程序依然在跑。因此timeid須要是全局變量
![]()
//簡單版本
let timeid;
function myInterval(fn,time){
function inner(){
fn();
timeid = setTimeout(inner,time)
}
inner();
return timeid;
}
myInterval(()=>{console.log('重複調用')},1000)
setTimeout(()=>{
clearTimeout (timeid)
},6000)
複製代碼