歡迎光臨
每天分享高質量文章

JS事件迴圈詳解

英文:blog.carbonfive.com

譯者:伯樂在線 – cucr

網址:http://web.jobbole.com/83360/


本文講什麼?

伴隨著JavaScript這種web瀏覽器腳本語言的普及,對它的事件驅動交互模型,以及它與Ruby、Python和Java中常見的請求-響應模型的區別有一個基本瞭解,對您是有益的。在這篇文章中,我將解釋一些JavaScript併發模型的核心概念,包括其事件迴圈和訊息佇列,希望能夠提升你對一種語言的理解,這種語言你可能已經在使用但也許並不完全理解。

這篇文章是寫給誰的?

這篇文章是針對在客戶端或服務器端使用或計劃使用JavaScript的web開發人員的。如果你已經精通事件迴圈,那麼這篇文章的大部分對你來說會很熟悉。對於那些還不是很精通的人,我希望能給你提供一個基本的瞭解,這樣可以更好地幫助你閱讀和編寫日常代碼。

非阻塞I / O

在JavaScript中,幾乎所有的I/O都是非阻塞的。這包括HTTP請求,資料庫操作和磁盤讀寫,單執行緒執行要求在運行期執行一個操作時,提供一個回呼函式,然後繼續做其它的事情。當操作已經完成時,訊息和已提供的回呼函式一起插入到佇列。在將來的某個時候,訊息從佇列移除,回呼函式觸發。

雖然這種交互模型可能對已經習慣使用用戶界面的開發人員很熟悉,比如“mousedown,”和“click”事件在某一時刻被觸發。這與通常在服務器端應用程式進行的同步式請求-響應模型是不同的。

讓我們來比較一下兩小塊代碼,發出HTTP請求到www.google.com和輸出響應到控制台。首先看看Ruby,配合使用Faraday(一個Ruby 的HTTP 客戶端開發庫):

response = Faraday.get ‘http://www.google.com’

puts response

puts ‘Done!’

執行路徑很容易跟蹤:

  1. 執行get方法,執行的執行緒等待,直到收到響應

  2. 從谷歌收到響應並傳回給呼叫者,它儲存在一個變數中

  3. 變數的值(在本例中,就是我們的響應)輸出到控制台

  4. 值“Done!“輸出到控制台

讓我們使用Node.js和Request庫在JavaScript做同樣的事情:

request(‘http://www.google.com’, function(error, response, body) {

console.log(body);

});

console.log(‘Done!’);

錶面上看略有不同,實際行為截然不同:

  1. 執行請求函式,傳遞一個匿名函式作為回呼,當響應在將來某個時候可用時執行回呼。

  2. “Done!“立即輸出到控制台

  3. 在將來的某個時候,響應傳回和回呼執行時,輸出它的內容到控制台

事件迴圈

將呼叫者和響應解耦,使得JavaScript在運行期等待異步操作完成和回呼觸發時可以做其他事情。但是這些回呼在記憶體中是如何組織的,按什麼順序執行?什麼導致他們被呼叫?

JavaScript運行時包含一個訊息佇列,它儲存了需要處理的訊息的串列和相關的回呼函式。這些訊息是以佇列的形式來響應回呼函式所涉及的外部事件(如滑鼠單擊或收到HTTP請求的響應)的。例如,如果用戶單擊一個按鈕,但沒有提供回呼函式,那麼也沒有訊息會被加入佇列。

在一次迴圈,佇列提取下一條訊息(每次提取稱為一次“tick”),當事件發生,該訊息的回呼執行。

回呼函式的呼叫在呼叫棧作為初始化frame(片段),由於JavaScript是單執行緒的,未來的訊息提取和處理因為等待棧的所有呼叫傳回而被停止。後續(同步)函式呼叫會添加新的呼叫frame到棧(例如,函式init呼叫函式changeColor)。

function init() {

var link = document.getElementById(“foo”);

link.addEventListener(“click”, function changeColor() {

this.style.color = “burlywood”;

});

}

init();

在這個例子中,當用戶單擊“foo”元素時,一條訊息(及其回呼函式changeColor)會被插入到佇列,並觸發“onclick“事件。當訊息離開佇列時,其回呼函式changeColor被呼叫。當changeColor傳回(或者是丟擲一個錯誤),事件迴圈仍在繼續。只要函式changeColor存在,並指定為“foo”元素的onclick方法的回呼,那麼在該元素上單擊會導致更多的訊息(和相關的回呼changeColor)插入佇列。

佇列附加訊息

如果一個函式在代碼中按異步呼叫(比如setTimeout),提供的回呼將最終作為一個不同的訊息佇列的一部分被執行,它將發生在事件迴圈的某個未來的動作上。例如:

function f() {

console.log(“foo”);

setTimeout(g, 0);

console.log(“baz”);

h();

}

function g() {

console.log(“bar”);

}

function h() {

console.log(“blix”);

}

f();

由於setTimeout的非阻塞特性,它的回呼將在至少0毫秒後觸發,而不是作為訊息的一部分被處理。在這個示例中,setTimeout被呼叫, 傳入了一個回呼函式g且延時0毫秒後執行。當我們指定時間到達(當前情況是,幾乎立即執行),一個單獨的訊息將被加入佇列(g作為回呼函式)。控制台打印的結果會是像這樣:“foo”,“baz”,“blix”,然後是事件迴圈的下一個動作:“bar”。如果在同一個呼叫片段中,兩個呼叫都設置為setTimeout -傳遞給第二個引數的值也相同-則它們的回呼將按照呼叫順序插入佇列。

Web Workers

使用Web Workers允許您能夠將一項費時的操作在一個單獨的執行緒中執行,從而可以釋放主執行緒去做別的事情。worker(工作執行緒)包括一個獨立的訊息佇列,事件循 環,記憶體空間獨立於實體化它的原始執行緒。worker和主執行緒之間的通信通過訊息傳遞,看起來很像我們往常常見的傳統事件代碼示例。

首先,我們的worker:

// our worker, which does some CPU-intensive operation

var reportResult = function(e) {

pi = SomeLib.computePiToSpecifiedDecimals(e.data);

postMessage(pi);

};

onmessage = reportResult;

然後,主要的代碼塊在我們的HTML中以script-標簽存在:

// our main code, in a

分享創造快樂