成人无码视频,亚洲精品久久久久av无码,午夜精品久久久久久毛片,亚洲 中文字幕 日韩 无码

資訊專欄INFORMATION COLUMN

前端基礎(chǔ)進(jìn)階(十二):深入核心,詳解事件循環(huán)機(jī)制

whjin / 1953人閱讀

摘要:前端基礎(chǔ)進(jìn)階正是圍繞這條線索慢慢展開,而事件循環(huán)機(jī)制,則是這條線索的最關(guān)鍵的知識(shí)點(diǎn)。特別是中正式加入了對(duì)象之后,對(duì)于新標(biāo)準(zhǔn)中事件循環(huán)機(jī)制的理解就變得更加重要。之后全局上下文進(jìn)入函數(shù)調(diào)用棧。

JavaScript的學(xué)習(xí)零散而龐雜,因此很多時(shí)候我們學(xué)到了一些東西,但是卻沒辦法感受到自己的進(jìn)步,甚至過了不久,就把學(xué)到的東西給忘了。為了解決自己的這個(gè)困擾,在學(xué)習(xí)的過程中,我一直試圖在尋找一條核心的線索,只要我根據(jù)這條線索,我就能夠一點(diǎn)一點(diǎn)的進(jìn)步。

前端基礎(chǔ)進(jìn)階正是圍繞這條線索慢慢展開,而事件循環(huán)機(jī)制(Event Loop),則是這條線索的最關(guān)鍵的知識(shí)點(diǎn)。所以,我就馬不停蹄的去深入的學(xué)習(xí)了事件循環(huán)機(jī)制,并總結(jié)出了這篇文章跟大家分享。

事件循環(huán)機(jī)制從整體上的告訴了我們所寫的JavaScript代碼的執(zhí)行順序。但是在我學(xué)習(xí)的過程中,找到的許多國內(nèi)博客文章對(duì)于它的講解淺嘗輒止,不得其法,很多文章在圖中畫個(gè)圈就表示循環(huán)了,看了之后也沒感覺明白了多少。但是他又如此重要,以致于當(dāng)我們想要面試中高級(jí)崗位時(shí),事件循環(huán)機(jī)制總是繞不開的話題。特別是ES6中正式加入了Promise對(duì)象之后,對(duì)于新標(biāo)準(zhǔn)中事件循環(huán)機(jī)制的理解就變得更加重要。這就很尷尬了。

最近有兩篇比較火的文章也表達(dá)了這個(gè)問題的重要性。

這個(gè)前端面試在搞事
80% 應(yīng)聘者都不及格的 JS 面試題

但是很遺憾的是,大神們告訴了大家這個(gè)知識(shí)點(diǎn)很重要,卻并沒有告訴大家為什么會(huì)這樣。所以當(dāng)我們在面試時(shí)遇到這樣的問題時(shí),就算你知道了結(jié)果,面試官再進(jìn)一步問一下,我們依然懵逼。

在學(xué)習(xí)事件循環(huán)機(jī)制之前,我默認(rèn)你已經(jīng)懂得了如下概念,如果仍然有疑問,可以回過頭去看看我以前的文章。

執(zhí)行上下文(Execution context)

函數(shù)調(diào)用棧(call stack)

隊(duì)列數(shù)據(jù)結(jié)構(gòu)(queue)

Promise(我會(huì)在下一篇文章專門總結(jié)Promise的詳細(xì)使用)

因?yàn)閏hrome瀏覽器中新標(biāo)準(zhǔn)中的事件循環(huán)機(jī)制與nodejs類似,因此此處就整合nodejs一起來理解,其中會(huì)介紹到幾個(gè)nodejs有,但是瀏覽器中沒有的API,大家只需要了解就好,不一定非要知道她是如何使用。比如process.nextTick,setImmediate

OK,那我就先拋出結(jié)論,然后以例子與圖示詳細(xì)給大家演示事件循環(huán)機(jī)制。

我們知道JavaScript的一大特點(diǎn)就是單線程,而這個(gè)線程中擁有唯一的一個(gè)事件循環(huán)。

當(dāng)然新標(biāo)準(zhǔn)中的web worker涉及到了多線程,我對(duì)它了解也不多,這里就不討論了。

JavaScript代碼的執(zhí)行過程中,除了依靠函數(shù)調(diào)用棧來搞定函數(shù)的執(zhí)行順序外,還依靠任務(wù)隊(duì)列(task queue)來搞定另外一些代碼的執(zhí)行。

一個(gè)線程中,事件循環(huán)是唯一的,但是任務(wù)隊(duì)列可以擁有多個(gè)。

任務(wù)隊(duì)列又分為macro-task(宏任務(wù))與micro-task(微任務(wù)),在最新標(biāo)準(zhǔn)中,它們被分別稱為task與jobs。

macro-task大概包括:script(整體代碼), setTimeout, setInterval, setImmediate, I/O, UI rendering。

micro-task大概包括: process.nextTick, Promise, Object.observe(已廢棄), MutationObserver(html5新特性)

setTimeout/Promise等我們稱之為任務(wù)源。而進(jìn)入任務(wù)隊(duì)列的是他們指定的具體執(zhí)行任務(wù)。

// setTimeout中的回調(diào)函數(shù)才是進(jìn)入任務(wù)隊(duì)列的任務(wù)
setTimeout(function() {
    console.log("xxxx");
})
// 非常多的同學(xué)對(duì)于setTimeout的理解存在偏差。所以大概說一下誤解:
// setTimeout作為一個(gè)任務(wù)分發(fā)器,這個(gè)函數(shù)會(huì)立即執(zhí)行,而它所要分發(fā)的任務(wù),也就是它的第一個(gè)參數(shù),才是延遲執(zhí)行

來自不同任務(wù)源的任務(wù)會(huì)進(jìn)入到不同的任務(wù)隊(duì)列。其中setTimeout與setInterval是同源的。

事件循環(huán)的順序,決定了JavaScript代碼的執(zhí)行順序。它從script(整體代碼)開始第一次循環(huán)。之后全局上下文進(jìn)入函數(shù)調(diào)用棧。直到調(diào)用棧清空(只剩全局),然后執(zhí)行所有的micro-task。當(dāng)所有可執(zhí)行的micro-task執(zhí)行完畢之后。循環(huán)再次從macro-task開始,找到其中一個(gè)任務(wù)隊(duì)列執(zhí)行完畢,然后再執(zhí)行所有的micro-task,這樣一直循環(huán)下去。

其中每一個(gè)任務(wù)的執(zhí)行,無論是macro-task還是micro-task,都是借助函數(shù)調(diào)用棧來完成。

純文字表述確實(shí)有點(diǎn)干澀,因此,這里我們通過2個(gè)例子,來逐步理解事件循環(huán)的具體順序。

// demo01  出自于上面我引用文章的一個(gè)例子,我們來根據(jù)上面的結(jié)論,一步一步分析具體的執(zhí)行過程。
// 為了方便理解,我以打印出來的字符作為當(dāng)前的任務(wù)名稱
setTimeout(function() {
    console.log("timeout1");
})

new Promise(function(resolve) {
    console.log("promise1");
    for(var i = 0; i < 1000; i++) {
        i == 99 && resolve();
    }
    console.log("promise2");
}).then(function() {
    console.log("then1");
})

console.log("global1");

首先,事件循環(huán)從宏任務(wù)隊(duì)列開始,這個(gè)時(shí)候,宏任務(wù)隊(duì)列中,只有一個(gè)script(整體代碼)任務(wù)。每一個(gè)任務(wù)的執(zhí)行順序,都依靠函數(shù)調(diào)用棧來搞定,而當(dāng)遇到任務(wù)源時(shí),則會(huì)先分發(fā)任務(wù)到對(duì)應(yīng)的隊(duì)列中去,所以,上面例子的第一步執(zhí)行如下圖所示。

第二步:script任務(wù)執(zhí)行時(shí)首先遇到了setTimeout,setTimeout為一個(gè)宏任務(wù)源,那么他的作用就是將任務(wù)分發(fā)到它對(duì)應(yīng)的隊(duì)列中。

setTimeout(function() {
    console.log("timeout1");
})

第三步:script執(zhí)行時(shí)遇到Promise實(shí)例。Promise構(gòu)造函數(shù)中的第一個(gè)參數(shù),是在new的時(shí)候執(zhí)行,因此不會(huì)進(jìn)入任何其他的隊(duì)列,而是直接在當(dāng)前任務(wù)直接執(zhí)行了,而后續(xù)的.then則會(huì)被分發(fā)到micro-task的Promise隊(duì)列中去。

因此,構(gòu)造函數(shù)執(zhí)行時(shí),里面的參數(shù)進(jìn)入函數(shù)調(diào)用棧執(zhí)行。for循環(huán)不會(huì)進(jìn)入任何隊(duì)列,因此代碼會(huì)依次執(zhí)行,所以這里的promise1和promise2會(huì)依次輸出。

script任務(wù)繼續(xù)往下執(zhí)行,最后只有一句輸出了globa1,然后,全局任務(wù)就執(zhí)行完畢了。

第四步:第一個(gè)宏任務(wù)script執(zhí)行完畢之后,就開始執(zhí)行所有的可執(zhí)行的微任務(wù)。這個(gè)時(shí)候,微任務(wù)中,只有Promise隊(duì)列中的一個(gè)任務(wù)then1,因此直接執(zhí)行就行了,執(zhí)行結(jié)果輸出then1,當(dāng)然,他的執(zhí)行,也是進(jìn)入函數(shù)調(diào)用棧中執(zhí)行的。

第五步:當(dāng)所有的micro-tast執(zhí)行完畢之后,表示第一輪的循環(huán)就結(jié)束了。這個(gè)時(shí)候就得開始第二輪的循環(huán)。第二輪循環(huán)仍然從宏任務(wù)macro-task開始。

這個(gè)時(shí)候,我們發(fā)現(xiàn)宏任務(wù)中,只有在setTimeout隊(duì)列中還要一個(gè)timeout1的任務(wù)等待執(zhí)行。因此就直接執(zhí)行即可。

這個(gè)時(shí)候宏任務(wù)隊(duì)列與微任務(wù)隊(duì)列中都沒有任務(wù)了,所以代碼就不會(huì)再輸出其他東西了。

那么上面這個(gè)例子的輸出結(jié)果就顯而易見。大家可以自行嘗試體會(huì)。

這個(gè)例子比較簡答,涉及到的隊(duì)列任務(wù)并不多,因此讀懂了它還不能全面的了解到事件循環(huán)機(jī)制的全貌。所以我下面弄了一個(gè)復(fù)雜一點(diǎn)的例子,再給大家解析一番,相信讀懂之后,事件循環(huán)這個(gè)問題,再面試中再次被問到就難不倒大家了。

// demo02
console.log("golb1");

setTimeout(function() {
    console.log("timeout1");
    process.nextTick(function() {
        console.log("timeout1_nextTick");
    })
    new Promise(function(resolve) {
        console.log("timeout1_promise");
        resolve();
    }).then(function() {
        console.log("timeout1_then")
    })
})

setImmediate(function() {
    console.log("immediate1");
    process.nextTick(function() {
        console.log("immediate1_nextTick");
    })
    new Promise(function(resolve) {
        console.log("immediate1_promise");
        resolve();
    }).then(function() {
        console.log("immediate1_then")
    })
})

process.nextTick(function() {
    console.log("glob1_nextTick");
})
new Promise(function(resolve) {
    console.log("glob1_promise");
    resolve();
}).then(function() {
    console.log("glob1_then")
})

setTimeout(function() {
    console.log("timeout2");
    process.nextTick(function() {
        console.log("timeout2_nextTick");
    })
    new Promise(function(resolve) {
        console.log("timeout2_promise");
        resolve();
    }).then(function() {
        console.log("timeout2_then")
    })
})

process.nextTick(function() {
    console.log("glob2_nextTick");
})
new Promise(function(resolve) {
    console.log("glob2_promise");
    resolve();
}).then(function() {
    console.log("glob2_then")
})

setImmediate(function() {
    console.log("immediate2");
    process.nextTick(function() {
        console.log("immediate2_nextTick");
    })
    new Promise(function(resolve) {
        console.log("immediate2_promise");
        resolve();
    }).then(function() {
        console.log("immediate2_then")
    })
})

這個(gè)例子看上去有點(diǎn)復(fù)雜,亂七八糟的代碼一大堆,不過不用擔(dān)心,我們一步一步來分析一下。

第一步:宏任務(wù)script首先執(zhí)行。全局入棧。glob1輸出。

第二步,執(zhí)行過程遇到setTimeout。setTimeout作為任務(wù)分發(fā)器,將任務(wù)分發(fā)到對(duì)應(yīng)的宏任務(wù)隊(duì)列中。

setTimeout(function() {
    console.log("timeout1");
    process.nextTick(function() {
        console.log("timeout1_nextTick");
    })
    new Promise(function(resolve) {
        console.log("timeout1_promise");
        resolve();
    }).then(function() {
        console.log("timeout1_then")
    })
})

第三步:執(zhí)行過程遇到setImmediate。setImmediate也是一個(gè)宏任務(wù)分發(fā)器,將任務(wù)分發(fā)到對(duì)應(yīng)的任務(wù)隊(duì)列中。setImmediate的任務(wù)隊(duì)列會(huì)在setTimeout隊(duì)列的后面執(zhí)行。

setImmediate(function() {
    console.log("immediate1");
    process.nextTick(function() {
        console.log("immediate1_nextTick");
    })
    new Promise(function(resolve) {
        console.log("immediate1_promise");
        resolve();
    }).then(function() {
        console.log("immediate1_then")
    })
})

第四步:執(zhí)行遇到nextTick,process.nextTick是一個(gè)微任務(wù)分發(fā)器,它會(huì)將任務(wù)分發(fā)到對(duì)應(yīng)的微任務(wù)隊(duì)列中去。

process.nextTick(function() {
    console.log("glob1_nextTick");
})

第五步:執(zhí)行遇到Promise。Promise的then方法會(huì)將任務(wù)分發(fā)到對(duì)應(yīng)的微任務(wù)隊(duì)列中,但是它構(gòu)造函數(shù)中的方法會(huì)直接執(zhí)行。因此,glob1_promise會(huì)第二個(gè)輸出。

new Promise(function(resolve) {
    console.log("glob1_promise");
    resolve();
}).then(function() {
    console.log("glob1_then")
})

第六步:執(zhí)行遇到第二個(gè)setTimeout。

setTimeout(function() {
    console.log("timeout2");
    process.nextTick(function() {
        console.log("timeout2_nextTick");
    })
    new Promise(function(resolve) {
        console.log("timeout2_promise");
        resolve();
    }).then(function() {
        console.log("timeout2_then")
    })
})

第七步:先后遇到nextTick與Promise

process.nextTick(function() {
    console.log("glob2_nextTick");
})
new Promise(function(resolve) {
    console.log("glob2_promise");
    resolve();
}).then(function() {
    console.log("glob2_then")
})

第八步:再次遇到setImmediate。

setImmediate(function() {
    console.log("immediate2");
    process.nextTick(function() {
        console.log("immediate2_nextTick");
    })
    new Promise(function(resolve) {
        console.log("immediate2_promise");
        resolve();
    }).then(function() {
        console.log("immediate2_then")
    })
})

這個(gè)時(shí)候,script中的代碼就執(zhí)行完畢了,執(zhí)行過程中,遇到不同的任務(wù)分發(fā)器,就將任務(wù)分發(fā)到各自對(duì)應(yīng)的隊(duì)列中去。接下來,將會(huì)執(zhí)行所有的微任務(wù)隊(duì)列中的任務(wù)。

其中,nextTick隊(duì)列會(huì)比Promie先執(zhí)行。nextTick中的可執(zhí)行任務(wù)執(zhí)行完畢之后,才會(huì)開始執(zhí)行Promise隊(duì)列中的任務(wù)。

當(dāng)所有可執(zhí)行的微任務(wù)執(zhí)行完畢之后,這一輪循環(huán)就表示結(jié)束了。下一輪循環(huán)繼續(xù)從宏任務(wù)隊(duì)列開始執(zhí)行。

這個(gè)時(shí)候,script已經(jīng)執(zhí)行完畢,所以就從setTimeout隊(duì)列開始執(zhí)行。

setTimeout任務(wù)的執(zhí)行,也依然是借助函數(shù)調(diào)用棧來完成,并且遇到任務(wù)分發(fā)器的時(shí)候也會(huì)將任務(wù)分發(fā)到對(duì)應(yīng)的隊(duì)列中去。

只有當(dāng)setTimeout中所有的任務(wù)執(zhí)行完畢之后,才會(huì)再次開始執(zhí)行微任務(wù)隊(duì)列。并且清空所有的可執(zhí)行微任務(wù)。

setTiemout隊(duì)列產(chǎn)生的微任務(wù)執(zhí)行完畢之后,循環(huán)則回過頭來開始執(zhí)行setImmediate隊(duì)列。仍然是先將setImmediate隊(duì)列中的任務(wù)執(zhí)行完畢,再執(zhí)行所產(chǎn)生的微任務(wù)。

當(dāng)setImmediate隊(duì)列執(zhí)行產(chǎn)生的微任務(wù)全部執(zhí)行之后,第二輪循環(huán)也就結(jié)束了。

大家需要注意這里的循環(huán)結(jié)束的時(shí)間節(jié)點(diǎn)。

當(dāng)我們在執(zhí)行setTimeout任務(wù)中遇到setTimeout時(shí),它仍然會(huì)將對(duì)應(yīng)的任務(wù)分發(fā)到setTimeout隊(duì)列中去,但是該任務(wù)就得等到下一輪事件循環(huán)執(zhí)行了。例子中沒有涉及到這么復(fù)雜的嵌套,大家可以動(dòng)手添加或者修改他們的位置來感受一下循環(huán)的變化。

OK,到這里,事件循環(huán)我想我已經(jīng)表述得很清楚了,能不能理解就看讀者老爺們有沒有耐心了。我估計(jì)很多人會(huì)理解不了循環(huán)結(jié)束的節(jié)點(diǎn)。

當(dāng)然,這些順序都是v8的一些實(shí)現(xiàn)。我們也可以根據(jù)上面的規(guī)則,來嘗試實(shí)現(xiàn)一下事件循環(huán)的機(jī)制。

// 用數(shù)組模擬一個(gè)隊(duì)列
var tasks = [];

// 模擬一個(gè)事件分發(fā)器
var addFn1 = function(task) {
    tasks.push(task);
}

// 執(zhí)行所有的任務(wù)
var flush = function() {
    tasks.map(function(task) {
        task();
    })
}

// 最后利用setTimeout/或者其他你認(rèn)為合適的方式丟入事件循環(huán)中
setTimeout(function() {
    flush();
})

// 當(dāng)然,也可以不用丟進(jìn)事件循環(huán),而是我們自己手動(dòng)在適當(dāng)?shù)臅r(shí)機(jī)去執(zhí)行對(duì)應(yīng)的某一個(gè)方法

var dispatch = function(name) {
    tasks.map(function(item) {
        if(item.name == name) {
            item.handler();
        }
    })
}

// 當(dāng)然,我們把任務(wù)丟進(jìn)去的時(shí)候,多保存一個(gè)name即可。
// 這時(shí)候,task的格式就如下
demoTask =  {
    name: "demo",
    handler: function() {}
}

// 于是,一個(gè)訂閱-通知的設(shè)計(jì)模式就這樣輕松的被實(shí)現(xiàn)了

這樣,我們就模擬了一個(gè)任務(wù)隊(duì)列。我們還可以定義另外一個(gè)隊(duì)列,利用上面的各種方式來規(guī)定他們的優(yōu)先級(jí)。

需要注意的是,這里的執(zhí)行順序,或者執(zhí)行的優(yōu)先級(jí)在不同的場景里由于實(shí)現(xiàn)的不同會(huì)導(dǎo)致不同的結(jié)果,包括node的不同版本,不同瀏覽器等都有不同的結(jié)果。

前端基礎(chǔ)進(jìn)階系列目錄

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://m.hztianpu.com/yun/90544.html

相關(guān)文章

  • 前端基礎(chǔ)進(jìn)階目錄

    摘要:不過其實(shí)簡書文章評(píng)論里有很多大家的問題以及解答,對(duì)于進(jìn)一步理解文中知識(shí)幫助很大的,算是有點(diǎn)可惜吧。不過也希望能夠?qū)φ趯W(xué)習(xí)前端的你有一些小幫助。如果在閱讀中發(fā)現(xiàn)了一些錯(cuò)誤,請(qǐng)?jiān)谠u(píng)論里告訴我,我會(huì)及時(shí)更改。 前端基礎(chǔ)進(jìn)階(一):內(nèi)存空間詳細(xì)圖解 前端基礎(chǔ)進(jìn)階(二):執(zhí)行上下文詳細(xì)圖解 前端基礎(chǔ)進(jìn)階(三):變量對(duì)象詳解 前端基礎(chǔ)進(jìn)階(四):詳細(xì)圖解作用域鏈與閉包 前端基礎(chǔ)進(jìn)階(五):全方位...

    mo0n1andin 評(píng)論0 收藏0
  • JavaScript 的事件機(jī)制

    摘要:的事件機(jī)制關(guān)于,查閱多篇博客,或多或少總有些出入,在此寫下自己關(guān)于的理解按同步與異步分首先判斷是同步還是異步同步就進(jìn)入主進(jìn)程異步就進(jìn)入異步任務(wù)在中注冊函數(shù)當(dāng)滿足觸發(fā)條件后被推入同步任務(wù)進(jìn)入主線程后一直執(zhí)行直到主線程空閑時(shí)才會(huì)去中查看是否有可 JavaScript 的事件機(jī)制 關(guān)于Event Loop,查閱多篇博客,或多或少總有些出入,在此寫下自己關(guān)于Event Loop的理解 按同步與...

    Turbo 評(píng)論0 收藏0
  • 深入淺出JavaScript運(yùn)行機(jī)制

    摘要:主線程從任務(wù)隊(duì)列中讀取事件,這個(gè)過程是循環(huán)不斷的,所以整個(gè)的這種運(yùn)行機(jī)制又稱為事件循環(huán)。上面也提到,在到達(dá)指定時(shí)間時(shí),定時(shí)器就會(huì)將相應(yīng)回調(diào)函數(shù)插入任務(wù)隊(duì)列尾部。這就是定時(shí)器功能。關(guān)于定時(shí)器的重要補(bǔ)充定時(shí)器包括與兩個(gè)方法。 一、引子 本文介紹JavaScript運(yùn)行機(jī)制,這一部分比較抽象,我們先從一道面試題入手: console.log(1); setTimeout(function()...

    mochixuan 評(píng)論0 收藏0
  • 深入淺出JavaScript運(yùn)行機(jī)制

    摘要:主線程從任務(wù)隊(duì)列中讀取事件,這個(gè)過程是循環(huán)不斷的,所以整個(gè)的這種運(yùn)行機(jī)制又稱為事件循環(huán)。上面也提到,在到達(dá)指定時(shí)間時(shí),定時(shí)器就會(huì)將相應(yīng)回調(diào)函數(shù)插入任務(wù)隊(duì)列尾部。這就是定時(shí)器功能。關(guān)于定時(shí)器的重要補(bǔ)充定時(shí)器包括與兩個(gè)方法。 一、引子 本文介紹JavaScript運(yùn)行機(jī)制,這一部分比較抽象,我們先從一道面試題入手: console.log(1); setTimeout(function()...

    魏明 評(píng)論0 收藏0
  • 深入淺出JavaScript運(yùn)行機(jī)制

    摘要:主線程從任務(wù)隊(duì)列中讀取事件,這個(gè)過程是循環(huán)不斷的,所以整個(gè)的這種運(yùn)行機(jī)制又稱為事件循環(huán)。上面也提到,在到達(dá)指定時(shí)間時(shí),定時(shí)器就會(huì)將相應(yīng)回調(diào)函數(shù)插入任務(wù)隊(duì)列尾部。這就是定時(shí)器功能。關(guān)于定時(shí)器的重要補(bǔ)充定時(shí)器包括與兩個(gè)方法。 一、引子 本文介紹JavaScript運(yùn)行機(jī)制,這一部分比較抽象,我們先從一道面試題入手: console.log(1); setTimeout(function()...

    chaosx110 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

閱讀需要支付1元查看
<