本文主要介紹Javascript事件循環在 瀏覽器上的一些特性和應用介紹。
JavaScript的併發模型基於"事件循環"(Event Loop)。這個模型與像C或者Java這種其它語言中的模型大相徑庭。它永不阻塞,處理I/O一般經過事件和回調來執行,因此當一個應用正等待IndexedDB查詢返回或者一個XHR請求返回時,它仍然能夠處理其它事情,如用戶輸入。【參:併發模型與事件循環】javascript
只有一個主線程(one thread),而且只有一個調用棧(Call Stack),所以同一時間只能執行同一件事情。【參:Philip Roberts: What the heck is the event loop anyway? | JSConf EU (4:15)】html
執行上下文(Execution Context)java
Javascript代碼執行時,會進入一個執行上下文。它能夠理解爲當前代碼的運行環境(包括三種:全局環境、函數環境、Eval環境)。【參:Javascript核心技術開發解密 Page-11】git
糾正一點分享會可能存在的錯誤,它和做用域(Scope)不一樣!做用域是針對變量的一個可訪問區域,而執行上下文是屬於函數的指向的對象。(Scope pertains to the visibility of variables, and context refers to the object to which a function belongs.)【參: Why Should We Care About Scope and Context ?】
函數調用造成了一個棧幀。JavaScript中叫作調用棧(Call Stack);先進後出,後進先出(LIFO)。github
對象被分配在一個堆中,即用以表示一個大部分非結構化的內存區域。ajax
一個JavaScript運行時包含了一個待處理的消息隊列。每個消息都有一個爲了處理這個消息相關聯的函數。編程
任務(Task)api
主要是隊列中要執行的函數。主要包含如下兩大類:promise
// This is assuming that you're using jQuery jQuery.ajax({ url: 'https://api.example.com/endpoint', success: function(response) { // This is your callback. }, async: false // And this is a terrible idea }); // 原文網址:https://itw01.com/2Z6WE2L.html
這裏使用了JQuery的Ajax函數,併爲參數設置爲同步執行。那麼將遇到一種可怕的狀況,這段代碼在success回調前,後面的Javascript代碼將再也不執行。也就形成了可怕的阻塞(blocking)。瀏覽器
let bar = 0 function foo() { bar++ if (bar > 0) { return foo() } } foo()
沒錯,若是你不暈,說明你太棒了。這段代碼也會產生嚴重的問題。以下圖:
這是典型的內存溢出,可能會出如今某些場景下須要遞歸,但業務邏輯中的判斷又沒能正常計算進入到預設狀況,因而調用棧中不斷進入foo(),又沒法執行完,就形成內存溢出了。
糾正一處分享會中的錯誤,這個入棧過程沒有任何函數退出,因此會只進不出,致使內存爆炸。另外道哥提到的不斷累加到最大值爲負數的狀況,我測試了一下JS下,會變成Infinite。某些其餘語言(例如:C)是會變成-1,和二進制進位有關。
setTimeout(() => { console.log(1); }, 0); console.log(2); for (let i = 0; i < 3; i++) { console.log(i); } console.log(4);
輸出結果:
A: 1, 2, 0, 1, 2, 4
B: 2, 4, 0, 1, 2, 1
C: 2, 0, 1, 2, 4, 1
D: 2, 4, 0, 1, 2, 1
console.log(1); for (let i = 0; i < 3; i++) { setTimeout(() => { console.log('2-' + i); }, 0); } console.log(3);
輸出結果:
A: 1, 2-2, 2-2, 2-2, 3
B: 1, 3, 2-2, 2-2, 2-2
C: 1, 2-0, 2-1, 2-2, 3
D: 1, 3, 2-0, 2-1, 2-2
我想你們應該都正確答出來了吧:D,接下來我將詳細分析一些示例,以便於理解事件循環。
這裏我借用了做者稀土掘金
的深刻理解事件迴圈和非同步流程控制文中的一段示範。
console.log('Hi') setTimeout(function cb1() { console.log('cb1') }, 5000) console.log('Bye')
不管是否懂得事件循環的初學者,看到這段代碼應該也能猜出來答案是: Hi Bye cb1
。畢竟cb1有一個5s的定時器。可是執行細節是怎樣的呢。咱們來看下面這張gif圖。
圖中已經很清楚的展現了整個Javascript代碼是如何運做的。相信你們已經有較大的收穫了。
咱們來看這個頁面中的Javascript部分:
function one() { throw new Error('Oops!') } function two() { one() } function three() { two() } three()
咱們在瀏覽器端執行時,打個斷點在throw new Error('Oops!')
這一行。以下圖:
在瞭解了事件循環的執行順序後,咱們能夠輕鬆知道他的執行順序,經過Chrome開發者工具、咱們觀察圖中Call Stack區域,箭頭指向的one
也正是咱們斷點的地方,下面依次是two、three、(anonymous),這個是徹底符合棧的先進後出,後進先出(last-in-first-out)的特徵~
咱們在實際開發中,也能夠經過Call Stack裏面觀察,找出上一層入口,分析異常緣由。會有很大的幫助呢~
接着關閉斷點繼續執行,瀏覽器會拋出錯誤,錯誤信息以下,也是符合棧特色的
該文章中有部份內容在我製做的PPT中並未體現出來,對於此次分享會,我對Javascript一些運行機制有更深的理解,因爲時間倉促也就在本次分享作了一點入門介紹。下期我將會結合更多的示例,對Node.js的事件循環與瀏覽器端的差別等等進行更深刻的介紹,當你們都有所收穫後,就大可忘記了。
Philip Roberts: What the heck is the event loop anyway? | JSConf EU ★★★★★
這是一段來自Youtube的演講視頻,視頻中有用到一個工具" loupe - 模擬執行順序的工具",值得研究! ★★★★★
Tasks, microtasks, queues and schedules ★★★★★
文中有例子經過動畫來展現執行順序問題感受超級棒!也對不通瀏覽器的結果有作分析,固然也許部份內容有些不一致,須要注意。