Event Loop
是計算機系統的一種運行機制,是個很重要的概念。而Javascript
用這種機制來解決單線程運行帶來的問題。理解很熟悉將會有利於咱們更容易理解Vue
的異步事件。html
更多優質文章請猛戳GitHub博客,歡迎帥哥美女前來Star!!!git
單線程在程序執行時,所走的程序路徑按照連續順序排下來,前面的必須處理好,後面的纔會執行。簡單來講,即同一時間只能作一件事件。github
Js
是一種運行在網頁的簡單的腳本語言,因爲設計的初衷是做爲瀏覽器腳本語言,用於與用戶互動,以及操做DOM
。這決定它是單線程的。瀏覽器
單線程就意味着,全部任務都須要排隊,前一個任務結束,纔會執行後一個任務。若是前一個任務耗時很長,後一個任務就須要一直等着。這就會致使IO
操做(耗時但cpu閒置)時形成性能浪費的問題。bash
採用異步能夠解決。主線程徹底能夠無論IO
操做,暫時掛起處於等待中的任務,先運行排在後面的任務。等到IO
操做返回告終果,再回過頭,把掛起的任務繼續執行下去。因而,全部任務能夠分紅兩種,一種是同步任務,另外一種是異步任務。異步
當Javascript
代碼執行的時候會將不一樣的變量存於內存中的不一樣位置:堆(heap)和棧(stack)中來加以區分。其中,堆裏存放着一些對象。而棧中則存放着一些基礎類型變量以及對象的指針。可是咱們這裏說的執行棧和上面這個棧的意義卻有些不一樣。函數
js 在執行可執行的腳本時,會通過如下步驟:oop
globalContext
,每當執行到一個函數調用時都會建立一個可執行上下文(execution context)EC
。EC
,因此 JavaScript
引擎建立了執行上下文棧(Execution context stack,ECS)來管理執行上下文。Js
會退出這個執行環境並把這個執行環境銷燬,回到上一個方法的執行環境。 這個過程反覆進行,直到執行棧中的代碼所有執行完畢。function fun3() {
console.log('fun3')
}
function fun2() {
fun3();
}
function fun1() {
fun2();
}
fun1();
複製代碼
當執行一個函數的時候,就會建立一個執行上下文,而且壓入執行上下文棧,當函數執行完畢的時候,就會將函數的執行上下文從棧中彈出。知道了這樣的工做原理,讓咱們來看看如何處理上面這段代碼:性能
1.執行全局代碼,建立全局執行上下文,全局上下文被壓入執行上下文棧學習
ECStack = [
globalContext
];
複製代碼
globalContext = {
VO: [global],
Scope: [globalContext.VO],
this: globalContext.VO
}
複製代碼
fun1
函數被建立,保存做用域鏈到函數的內部屬性[[scope]]
fun1.[[scope]] = [
globalContext.VO
];
複製代碼
fun1
函數,建立fun1
函數執行上下文,fun1
函數執行上下文被壓入執行上下文棧ECStack = [
fun1,
globalContext
];
複製代碼
fun1
函數執行上下文初始化:
1.複製函數 [[scope]]
屬性建立做用域鏈。
2.用 arguments
建立活動對象。
3.初始化活動對象,即加入形參、函數聲明、變量聲明。
4.將活動對象壓入fun1
做用域鏈頂端。 同時 f
函數被建立,保存做用域鏈到 f 函數的內部屬性[[scope]]
checkscopeContext = {
AO: {
arguments: {
length: 0
},
scope: undefined,
f: reference to function f(){}
},
Scope: [AO, globalContext.VO],
this: undefined
}
複製代碼
fun2()
函數,重複步驟4。ECStack = [
fun3
fun2,
fun1,
globalContext
];
複製代碼
fun3
執行完畢,從執行棧中彈出...一直到fun1
在瞭解事件循環以前,先要弄明白Js
的內存模型,這有助於更好的理解事件循環。
異步任務存放在任務隊列裏,異步任務分爲 宏任務(macrotask)與微任務(microtask),不一樣的API註冊的任務會依次進入自身對應的隊列中,而後等待Event Loop將它們依次壓入執行棧中執行。
宏任務主要包含:
script
(總體代碼)setTimeout
setInterval
I/O
、UI
交互事件setImmediate
(Node.js 環境)微任務主要包含:
Promise
MutaionObserver
process.nextTick
(Node.js 環境)咱們的JavaScript
的執行過程是單線程的,全部的任務能夠看作存放在兩個隊列中——執行隊列和事件隊列。
執行隊列裏面是全部同步代碼的任務,事件隊列裏面是全部異步代碼的宏任務,而咱們的微任務,是處在兩個隊列之間。
當JavaScript
執行時,優先執行完全部同步代碼,遇到對應的異步代碼,就會根據其任務類型存到對應隊列(宏任務放入事件隊列,微任務放入執行隊列以後,事件隊列以前);當執行完同步代碼以後,就會執行位於執行隊列和事件隊列之間的微任務,而後再執行事件隊列中的宏任務。
實例
new Promise(resolve => {
resolve(1);
Promise.resolve().then(() => {
// t2
console.log(2)
});
console.log(4)
}).then(t => {
// t1
console.log(t)
});
console.log(3);
複製代碼
這段代碼的流程大體以下:
script
任務先運行。首先遇到Promise
實例,構造函數首先執行,因此首先輸出了 4。此時microtask
的任務有 t2
和 t1
script
任務繼續運行,輸出 3
。至此,第一個宏任務執行完成。t2
和 t1
,分別輸出 2
和 1
綜上,上述代碼的輸出是:4321
主線程從任務隊列中讀取事件,這個過程是循環不斷的,因此整個的這種運行機制又稱爲Event Loop
(事件循環)。
從上圖咱們能夠看出:
事件循環其實並不難,多查閱資料,多看看相關例子就ok。但願只知其一;不知其二的童鞋抓緊學習。