這篇文章是寫給誰的?
這篇文章是針對在客戶端或服務器端使用或計劃使用JavaScript的web開發人員的。如果你已經精通事件循環,那么這篇文章的大部分對你來說會很熟悉。對于那些還不是很精通的人,我希望能給你提供一個基本的了解,這樣可以更好地幫助你閱讀和編寫日常代碼。
非阻塞I / O
在JavaScript中,幾乎所有的I/O都是非阻塞的。這包括HTTP請求,數據庫操作和磁盤讀寫,單線程執行要求在運行期執行一個操作時,提供一個回調函數,然后繼續做其它的事情。當操作已經完成時,消息和已提供的回調函數一起插入到隊列。在將來的某個時候,消息從隊列移除,回調函數觸發。
雖然這種交互模型可能對已經習慣使用用戶界面的開發人員很熟悉,比如“mousedown,”和“click”事件在某一時刻被觸發。這與通常在服務器端應用程序進行的同步式請求-響應模型是不同的。
讓我們來比較一下兩小塊代碼,發出HTTP請求到www.google.com和輸出響應到控制臺。首先看看Ruby,配合使用Faraday(一個Ruby 的HTTP 客戶端開發庫):
執行路徑很容易跟蹤:
1、執行get方法,執行的線程等待,直到收到響應
2、從谷歌收到響應并返回給調用者,它存儲在一個變量中
3、變量的值(在本例中,就是我們的響應)輸出到控制臺
4、值“Done!“輸出到控制臺
讓我們使用Node.js和Request庫在JavaScript做同樣的事情:
表面上看略有不同,實際行為截然不同:
1、執行請求函數,傳遞一個匿名函數作為回調,當響應在將來某個時候可用時執行回調。
2、“Done!“立即輸出到控制臺
3、在將來的某個時候,響應返回和回調執行時,輸出它的內容到控制臺
事件循環
將調用者和響應解耦,使得JavaScript在運行期等待異步操作完成和回調觸發時可以做其他事情。但是這些回調在內存中是如何組織的,按什么順序執行?什么導致他們被調用?
JavaScript運行時包含一個消息隊列,它存儲了需要處理的消息的列表和相關的回調函數。這些消息是以隊列的形式來響應回調函數所涉及的外部事件(如鼠標單擊或收到HTTP請求的響應)的。例如,如果用戶單擊一個按鈕,但沒有提供回調函數,那么也沒有消息會被加入隊列。
在一次循環,隊列提取下一條消息(每次提取稱為一次“tick”),當事件發生,該消息的回調執行。
回調函數的調用在調用棧作為初始化frame(片段),由于JavaScript是單線程的,未來的消息提取和處理因為等待棧的所有調用返回而被停止。后續(同步)函數調用會添加新的調用frame到棧(例如,函數init調用函數changeColor)。
在這個例子中,當用戶單擊“foo”元素時,一條消息(及其回調函數changeColor)會被插入到隊列,并觸發“onclick“事件。當消息離開隊列時,其回調函數changeColor被調用。當changeColor返回(或者是拋出一個錯誤),事件循環仍在繼續。只要函數changeColor存在,并指定為“foo”元素的onclick方法的回調,那么在該元素上單擊會導致更多的消息(和相關的回調changeColor)插入隊列。
隊列附加消息
如果一個函數在代碼中按異步調用(比如setTimeout),提供的回調將最終作為一個不同的消息隊列的一部分被執行,它將發生在事件循環的某個未來的動作上。例如:
由于setTimeout的非阻塞特性,它的回調將在至少0毫秒后觸發,而不是作為消息的一部分被處理。在這個示例中,setTimeout被調用, 傳入了一個回調函數g且延時0毫秒后執行。當我們指定時間到達(當前情況是,幾乎立即執行),一個單獨的消息將被加入隊列(g作為回調函數)??刂婆_打印的結果會是像這樣:“foo”,“baz”,“blix”,然后是事件循環的下一個動作:“bar”。如果在同一個調用片段中,兩個調用都設置為setTimeout -傳遞給第二個參數的值也相同-則它們的回調將按照調用順序插入隊列。
Web Workers
使用Web Workers允許您能夠將一項費時的操作在一個單獨的線程中執行,從而可以釋放主線程去做別的事情。worker(工作線程)包括一個獨立的消息隊列,事件循 環,內存空間獨立于實例化它的原始線程。worker和主線程之間的通信通過消息傳遞,看起來很像我們往常常見的傳統事件代碼示例。
首先,我們的worker:
然后,主要的代碼塊在我們的HTML中以script-標簽存在:
聲明:本網頁內容旨在傳播知識,若有侵權等問題請及時與本網聯系,我們將在第一時間刪除處理。TEL:177 7030 7066 E-MAIL:11247931@qq.com