相信不少人都看過Philip Roberts在JS-Conf上關於Event Loop的演講.promise
這篇文章就是用來記錄觀後感.瀏覽器
Call Stackbash
調用堆棧: 它是一個用於記錄函數調用的數據結構(後進先出)。 當咱們調用一個函數時候, 就會將其推入到堆棧中, 當一個函數返回時候, 就會將其推出堆棧的頂部. 另外注意同步代碼纔會按照順序立刻進入Call Stack. "異步代碼" 後面會講.數據結構
若是堆棧長時間被佔用或者堵塞就會致使咱們常說的blocking script.異步
看下面這段代碼: 咱們理解Call Stack的執行過程.函數
function foo (b) {
var a = 5
return a * b + 10
}
function bar (x) {
var y = 3
return foo(x * y)
}
console.log(bar(6)) // 100
複製代碼
// 執行過程以下
1. 首先找到即將開始執行的main函數
2. 從console.log(bar(6)) 開始執行代碼, 它被推到調用棧的底部.
3. 而後bar()被推到console.log(bar(6))的頂部
4. 以後foo()被推到bar()的頂部, 可是當它執行後當即返回, 被推出堆棧.
5. 接着將bar()彈出
6. 最後在console.log()被彈出,把返回值100打印在輸出臺.
複製代碼
如下圖一個錯誤堆棧爲例可能更明瞭:oop
有時候咱們把一個函數遞歸調用屢次, 會進入一個死循環. 而對於Chrome瀏覽器來講, 棧的大小是有限制的(16000楨), 因而它會拋出一個Max Stack錯誤.測試
Heapui
堆: 對象是分配在堆裏面. 說到堆內存就須要瞭解棧內存, 引用類型,基本類型.spa
引用類型,值大小不固定,棧內存中存放地址指向堆內存中的對象。是按引用訪問的。以下圖所示:棧內存中存放的只是該對象的訪問地址,在堆內存中爲這個值分配空間。因爲這種值的大小不固定,所以不能把它們保存到棧內存中。但內存地址大小的固定的,所以能夠將內存地址保存在棧內存中。 這樣,當查詢引用類型的變量時, 先從棧中讀取內存地址, 而後再經過地址找到堆中的值。對於這種,咱們把它叫作按引用訪問。
Quene
隊列: JS運行時候包含一個消息隊列, 它是要處理的消息列表(事件)和待執行的回調函數.
Quene是一個先進先出的數據結構,排在前面的事件,優先被主線程讀取。主線程的讀取過程基本上是自動的,只要執行棧(Call Stack)一清空,Quene上第一位的事件就自動進入主線程。可是,因爲存在"定時器"功能,主線程首先要檢查一下執行時間,某些事件只有到了規定的時間,才能進入到事件隊列,讀取事件,執行事件相應的回調.
基本上來講,這些事件是響應外部異步事件而排隊的(例如鼠標被點擊或接收到對HTTP請求的響應,固然若是一個事件沒有回調, 那麼事件是不會進入Quene排隊的.
這裏我以爲重點是理解什麼樣的代碼纔會進入到Quene, 我我的喜歡稱呼"異步代碼". 咱們都知道JS是單線程語音, 若是都採用同步不支持異步的話, 你能夠想象加載資源時候堆棧會堵塞成什麼樣子. 因此JS支持異步回調. 也許有人會問爲何異步代碼不能夠直接進入Call Stack, 其實想一想既然是異步代碼那麼進入到Call Stack的順序就是隨機插入的顯然不合理.
// 到了時間纔會推入到隊列
console.log(1)
setTimeout(() => {
console.log(2)
}, 200)
setTimeout(() => {
console.log(3)
}, 0)
console.log(4)
// 1 4 3 2
複製代碼
瀏覽器端異步事件: DOM事件,http請求,setTimeout等異步事件。
事件循環的基本工做是查看堆棧和任務隊列,當隊列看到堆棧爲空時,將隊列中的第一件event推送到堆棧。在處理任何其餘消息以前,會先處理當前事件的回調。
關於隊列: 其實分爲macro-task/micro-task.
// 測試代碼
function execOrder () {
setTimeout(() => console.log('timeout'), 0)
let promise = new Promise((resolve, reject) => {
console.log('Promise')
resolve()
})
promise
.then(() => {
console.log('resolved')
})
console.log('hi')
}
// 執行順序: Promise hi resolved timeout
複製代碼