国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 編程 > JavaScript > 正文

理解JS事件循環(huán)

2019-11-20 10:50:50
字體:
來源:轉載
供稿:網友

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

這篇文章是寫給誰的?

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

非阻塞I / O

在JavaScript中,幾乎所有的I/O都是非阻塞的。這包括HTTP請求,數據庫操作和磁盤讀寫,單線程執(zhí)行要求在運行期執(zhí)行一個操作時,提供一個回調函數,然后繼續(xù)做其它的事情。當操作已經完成時,消息和已提供的回調函數一起插入到隊列。在將來的某個時候,消息從隊列移除,回調函數觸發(fā)。

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

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

response = Faraday.get 'http://www.google.com'puts responseputs 'Done!'

執(zhí)行路徑很容易跟蹤:

1、執(zhí)行get方法,執(zhí)行的線程等待,直到收到響應
2、從谷歌收到響應并返回給調用者,它存儲在一個變量中
3、變量的值(在本例中,就是我們的響應)輸出到控制臺
4、值“Done!“輸出到控制臺
讓我們使用Node.js和Request庫在JavaScript做同樣的事情:

request('http://www.google.com', function(error, response, body) { console.log(body);}); console.log('Done!');

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

1、執(zhí)行請求函數,傳遞一個匿名函數作為回調,當響應在將來某個時候可用時執(zhí)行回調。
2、“Done!“立即輸出到控制臺
3、在將來的某個時候,響應返回和回調執(zhí)行時,輸出它的內容到控制臺
事件循環(huán)

將調用者和響應解耦,使得JavaScript在運行期等待異步操作完成和回調觸發(fā)時可以做其他事情。但是這些回調在內存中是如何組織的,按什么順序執(zhí)行?什么導致他們被調用?

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

在一次循環(huán),隊列提取下一條消息(每次提取稱為一次“tick”),當事件發(fā)生,該消息的回調執(zhí)行。

回調函數的調用在調用棧作為初始化frame(片段),由于JavaScript是單線程的,未來的消息提取和處理因為等待棧的所有調用返回而被停止。后續(xù)(同步)函數調用會添加新的調用frame到棧(例如,函數init調用函數changeColor)。

function init() { var link = document.getElementById("foo");  link.addEventListener("click", function changeColor() {  this.style.color = "burlywood"; });} init();

在這個例子中,當用戶單擊“foo”元素時,一條消息(及其回調函數changeColor)會被插入到隊列,并觸發(fā)“onclick“事件。當消息離開隊列時,其回調函數changeColor被調用。當changeColor返回(或者是拋出一個錯誤),事件循環(huán)仍在繼續(xù)。只要函數changeColor存在,并指定為“foo”元素的onclick方法的回調,那么在該元素上單擊會導致更多的消息(和相關的回調changeColor)插入隊列。

隊列附加消息

如果一個函數在代碼中按異步調用(比如setTimeout),提供的回調將最終作為一個不同的消息隊列的一部分被執(zhí)行,它將發(fā)生在事件循環(huán)的某個未來的動作上。例如:

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毫秒后觸發(fā),而不是作為消息的一部分被處理。在這個示例中,setTimeout被調用, 傳入了一個回調函數g且延時0毫秒后執(zhí)行。當我們指定時間到達(當前情況是,幾乎立即執(zhí)行),一個單獨的消息將被加入隊列(g作為回調函數)。控制臺打印的結果會是像這樣:“foo”,“baz”,“blix”,然后是事件循環(huán)的下一個動作:“bar”。如果在同一個調用片段中,兩個調用都設置為setTimeout -傳遞給第二個參數的值也相同-則它們的回調將按照調用順序插入隊列。

Web Workers

使用Web Workers允許您能夠將一項費時的操作在一個單獨的線程中執(zhí)行,從而可以釋放主線程去做別的事情。worker(工作線程)包括一個獨立的消息隊列,事件循 環(huán),內存空間獨立于實例化它的原始線程。worker和主線程之間的通信通過消息傳遞,看起來很像我們往常常見的傳統(tǒng)事件代碼示例。

首先,我們的worker:

// our worker, which does some CPU-intensive operationvar reportResult = function(e) { pi = SomeLib.computePiToSpecifiedDecimals(e.data); postMessage(pi);}; onmessage = reportResult;

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

// our main code, in a <script>-tag in our HTML pagevar piWorker = new Worker("pi_calculator.js");var logResult = function(e) { console.log("PI: " + e.data);}; piWorker.addEventListener("message", logResult, false);piWorker.postMessage(100000);

在這個例子中,主線程創(chuàng)建一個worker,同時注冊logResult回調函數到其“消息”事件。在worker里,reportResult函數注冊到自己的“消息”事件中。當worker線程接收到主線程的消息,worker入隊一條消息同時帶上reportResult回調函數。消息出隊時,一條新消息發(fā)送回主線程,新消息入隊主線程隊列(帶上logResult回調函數)。這樣,開發(fā)人員可以將cpu密集型操作委托給一個單獨的線程,使主線程解放出來繼續(xù)處理消息和事件。

關于閉包的

JavaScript對閉包的支持,允許你這樣注冊回調函數,當回調函數執(zhí)行時,保持了對他們被創(chuàng)建的環(huán)境的訪問(即使回調的執(zhí)行時創(chuàng)建了一個全新的調用棧)。理解我們的回調作為一個不同的消息的一部分被執(zhí)行,而不是創(chuàng)建它的那個會很有意思。看看下面的例子:

function changeHeaderDeferred() { var header = document.getElementById("header");  setTimeout(function changeHeader() {  header.style.color = "red";   return false; }, 100);  return false;} changeHeaderDeferred();

在這個例子中,changeHeaderDeferred函數被執(zhí)行時包含了變量header。函數 setTimeout被調用,導致消息(帶上changeHeader回調)被添加到消息隊列,在大約100毫秒后執(zhí)行。然后 changeHeaderDeferred函數返回false,結束第一個消息的處理,但header變量仍然可以通過閉包被引用,而不是被垃圾回收。當 第二個消息被處理(changeHeader函數),它保持了對在外部函數作用域中聲明的header變量的訪問。一旦第二個消息 (changeHeader函數)執(zhí)行結束,header變量可以被垃圾回收。

提醒

JavaScript 事件驅動的交互模型不同于許多程序員習慣的請求-響應模型,但如你所見,它并不復雜。使用簡單的消息隊列和事件循環(huán),JavaScript使得開發(fā)人員在構建他們的系統(tǒng)時使用大量asynchronously-fired(異步-觸發(fā))回調函數,讓運行時環(huán)境能在等待外部事件觸發(fā)的同時處理并發(fā)操作。然而,這不過是并發(fā)的一種方法。

以上就是本文的全部內容,希望對大家的學習有所幫助。

發(fā)表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發(fā)表
主站蜘蛛池模板: 禹州市| 邯郸县| 西乌| 当雄县| 随州市| 庆阳市| 紫云| 舟曲县| 津南区| 山阳县| 和硕县| 屏边| 武隆县| 洛宁县| 盱眙县| 沽源县| 炉霍县| 新沂市| 平武县| 临清市| 交口县| 湖北省| 鲁甸县| 河曲县| 郧西县| 广饶县| 乌鲁木齐县| 延川县| 鄂托克前旗| 新密市| 四会市| 炉霍县| 沙坪坝区| 张家港市| 招远市| 玉屏| 乐昌市| 苏尼特左旗| 巴楚县| 昌黎县| 荣成市|