我對 javascript 事件驅動機制的理解

以前在學習 javascript 在瀏覽器的應用時,也知道 javascript 是一個事件驅動語言,但對javascript事件驅動機制只有概念上的認識,所以,對異步機制也存在困惑。最近在接觸 node.js ,對javascript事件驅動機制有了更深的理解。javascript

概述

本文分別講解了javascript在瀏覽器端和服務器端(node.js)的事件驅動機制,期間加入了一些異步編程的例子加深理解。html

正文

javascript 在瀏覽器端的事件驅動機制前端

首先,javascript 在瀏覽器端運行是單線程的,這是由瀏覽器決定的,這是爲了不多線程執行不一樣任務會發生衝突的狀況。也就是說咱們寫的javascript 代碼只在一個線程上運行,稱之爲主線程(HTML5提供了web worker API可讓瀏覽器開一個線程運行比較複雜耗時的 javascript任務,可是這個線程仍受主線程的控制)。單線程的話,若是咱們作一些「sleep」的操做好比說:java

var now = + new Date()
while (+new Date() <= now + 1000){
//這是一個耗時的操所
}複製代碼

那麼在這將近一秒內,線程就會被阻塞,沒法繼續執行下面的任務。node

還有些操做好比說獲取遠程數據、I/O操做等,他們都很耗時,若是採用同步的方式,那麼進程在執行這些操做時就會由於耗時而等待,就像上面那樣,下面的任務也只能等待,這樣效率並不高。 爲了解決單線程帶來的阻塞問題不少操做系統實現了異步編程機制,瀏覽器中也是這麼作的,主要表現以下:web

  • 只在主線程中運行 javascript 代碼編程

  • 主線程一啓動就進入事件循環,整個過程就是不斷的循環,不斷地執行回調函數segmentfault

  • 遇到網絡請求、I/O操做等時,瀏覽器會單開工做線程來處理,並設置相應的觀察者,而後當即返回主線程,主線程繼續執行下面的任務後端

  • 瀏覽器開的線程處理好任務或者有監聽的事件後會用獲得的數據(或輸入)造成一個事件,放在相應觀察者的事件隊列中,事件隊列是在主線程中瀏覽器

  • 主線程不斷的循環,不斷檢查事件隊列,經過遍歷事件依次執行事件對應的回調函數

圖片來源segmentfault.com/img/bVxLvF)

注意:下圖中的消息隊列是存儲在主線程中

上圖中,假設你發起了一個AJAX請求,不管你把這個請求寫在什麼地方,它始終都在回調函數裏。由於事件驅動機制就是把一切抽象爲事件,代碼開始執行也是一個事件,也會隱式調用回調函數,調用回調函數就是開始執行代碼。而後主線程發起異步任務後就會隨即返回,繼續執行"代碼開始事件"對應回調函數裏下面的代碼,等到這個回調函數執行完畢,就會執行下一個事件。在這之間,Ajax線程會完成請求,而後把請求完成的事件(包含返回的數據)發送到事件隊尾中等待處理,等到主線程執行到這個事件時,指定的回調函數即被執行。

大概是這樣。若是有幾處疑問的話請往下看。下面結合代碼講一下具體的過程和機制。

console.log("開始");
setTimeout(function(){
  console.log('延遲執行的')
}, 1000);
setTimeout(function(){
  console.log('當即執行的')
}, 0);
console.log('結束') //開始 結束 當即執行的 延遲執行的複製代碼

watcher機制

watcher,觀察者,是事件驅動系統重要的機制。

setTimeout稱爲定時器,這是瀏覽器給的API。每當你使用定時器,這個函數將會設置一個watcher,觀察者。主線程會不斷的循環,不斷的"通過"這裏檢查時間,當主線程檢查時間間隔符合要求時,就會產生一個定時器事件,加入到這個watcher事件隊列中並執行回調函數。所以執行setTimeout只是在時間到的時候產生了要調用回調函數的消息加入到了事件隊列中,所以,回調函數並不必定在指定的時間時調用,它取決於前面有多少等待處理的事件。

剛纔講的是定時器觀察者,還有I/O觀察者、網絡請求觀察者、鼠標事件觀察者、鍵盤事件觀察者等等等等,咱們常常遇到事件監聽函數會讓你綁定一個回調函數,這種監聽函數通常就會設置watcher,其餘線程產生的事件也會放到相應watcher的事件隊列中,所以每一個watcher產生本身的事件隊列主線程在循環的時候,其實是在依次調用這些watcher,檢查每一個watcher的事件隊列,有事件就執行相應的回調。

(原創不容易,轉載請註明出處 : ),有錯誤請評論):

它的過程就是 :

  • 進程一啓動就進入事件循環

  • 有監聽就添加watcher

  • 遍歷watcher下的事件隊列

  • 執行下一個watcher

事件驅動機制,它會有各類各樣的事件,大量的事件,它所作的一切都跟處理事件有關。但並非全部的事件都有watcher,若是都有,主進程任務會變得很是繁重,何況有些事件咱們並不關心,例如你只寫了一個定時器,表明你關心這個事件,那麼點擊事件、網絡請求事件就不用關心,由於你根本就沒寫啊,也就沒有watcher

javascript 在 node.js上的事件驅動機制

javascript 在 node.js上的事件驅動機制與瀏覽器端大體相同,都是單線程,都有event loop,上面講的javascript在瀏覽器端的事件循環機制在node上也是大體同樣的,不一樣的是執行者何執行者的行爲不同,由於他們關注的任務不同:

  • node端異步機制和事件循環更加純粹一些。node爲了支持高併發,全部的API幾乎都是異步的,這樣會充分利用操做系統的其餘線程來幫忙完成任務,主線程只負責事件消費。例如當web server接收到請求,node就把它關閉,交給其餘線程進行處理,而後去服務下一個web請求。當這個請求完成,它被放處處理隊列,當到達隊列開頭,這個結果被返回給用戶。這樣的話webserver一直接受請求而不等待任何讀寫操做,這種非阻塞型I/O性能很強。

  • 瀏覽器端是瀏覽器負責執行BOM API,管理線程,處理用戶輸入信息等,在node上是node的一個核心庫libuv負責執行node API,管理主線程(運行javascript)和工做線程等。

  • 由於前端和後端關注的內容不一樣,所以兩個運行環境的API也專一於不一樣的任務

這就是我對事件驅動機制的理解,有錯誤歡迎指正!

推薦閱讀

JavaScript:完全理解同步、異步和事件循環(Event Loop)

【樸靈評註】JavaScript 運行機制詳解:再談Event Loop

單線程的JS引擎與 Event Loop

MDN:併發模型與事件循環

相關文章
相關標籤/搜索