亚洲中字慕日产2020,大陆极品少妇内射AAAAAA,无码av大香线蕉伊人久久,久久精品国产亚洲av麻豆网站

資訊專欄INFORMATION COLUMN

(轉(zhuǎn))JavaScript:同步、異步和事件循環(huán)

android_c / 1760人閱讀

摘要:事件循環(huán)事件循環(huán)是指主線程重復(fù)從消息隊(duì)列中取消息執(zhí)行的過程。事件觸發(fā)時(shí),表示異步任務(wù)完成,會(huì)將事件監(jiān)聽器函數(shù)封裝成一條消息放到消息隊(duì)列中,等待主線程執(zhí)行。

一. 單線程

我們常說“JavaScript是單線程的”。

所謂單線程,是指在JS引擎中負(fù)責(zé)解釋和執(zhí)行JavaScript代碼的線程只有一個(gè)。不妨叫它主線程。

但是實(shí)際上還存在其他的線程。例如:處理AJAX請(qǐng)求的線程、處理DOM事件的線程、定時(shí)器線程、讀寫文件的線程(例如在Node.js中)等等。這些線程可能存在于JS引擎之內(nèi),也可能存在于JS引擎之外,在此我們不做區(qū)分。不妨叫它們工作線程。

二. 同步和異步

假設(shè)存在一個(gè)函數(shù)A:

A(args...);

同步:如果在函數(shù)A返回的時(shí)候,調(diào)用者就能夠得到預(yù)期結(jié)果(即拿到了預(yù)期的返回值或者看到了預(yù)期的效果),那么這個(gè)函數(shù)就是同步的。

例如:

  Math.sqrt(2);
  console.log("Hi");

第一個(gè)函數(shù)返回時(shí),就拿到了預(yù)期的返回值:2的平方根。

第二個(gè)函數(shù)返回時(shí),就看到了預(yù)期的效果:在控制臺(tái)打印了一個(gè)字符串。

所以這兩個(gè)函數(shù)都是同步的。

異步:如果在函數(shù)A返回的時(shí)候,調(diào)用者還不能夠得到預(yù)期結(jié)果,而是需要在將來通過一定的手段得到,那么這個(gè)函數(shù)就是異步的。

例如:

fs.readFile("foo.txt", "utf8", function(err, data) {
    console.log(data);
});

在上面的代碼中,我們希望通過fs.readFile函數(shù)讀取文件foo.txt中的內(nèi)容,并打印出來。

但是在fs.readFile函數(shù)返回時(shí),我們期望的結(jié)果并不會(huì)發(fā)生,而是要等到文件全部讀取完成之后。如果文件很大的話可能要很長(zhǎng)時(shí)間。

下面以AJAX請(qǐng)求為例,來看一下同步和異步的區(qū)別:

**異步AJAX:**

主線程:“你好,AJAX線程。請(qǐng)你幫我發(fā)個(gè)HTTP請(qǐng)求吧,我把請(qǐng)求地址和參數(shù)都給你了?!?AJAX線程:“好的,主線程。我馬上去發(fā),但可能要花點(diǎn)兒時(shí)間呢,你可以先去忙別的?!?主線程::“謝謝,你拿到響應(yīng)后告訴我一聲啊?!?(接著,主線程做其他事情去了。一頓飯的時(shí)間后,它收到了響應(yīng)到達(dá)的通知。) 
**同步AJAX:**

主線程:“你好,AJAX線程。請(qǐng)你幫我發(fā)個(gè)HTTP請(qǐng)求吧,我把請(qǐng)求地址和參數(shù)都給你了。”

AJAX線程:“......”
主線程::“喂,AJAX線程,你怎么不說話?”
AJAX線程:“......”
主線程:“喂!喂喂喂!”
AJAX線程:“......”
(一炷香的時(shí)間后)
主線程::“喂!求你說句話吧!”
AJAX線程:“主線程,不好意思,我在工作的時(shí)候不能說話。你的請(qǐng)求已經(jīng)發(fā)完了,拿到響應(yīng)數(shù)據(jù)了,給你?!?

正是由于JavaScript是單線程的,而異步容易實(shí)現(xiàn)非阻塞,所以在JavaScript中對(duì)于耗時(shí)的操作或者時(shí)間不確定的操作,使用異步就成了必然的選擇。異步是這篇文章關(guān)注的重點(diǎn)。

三. 異步過程的構(gòu)成要素

從上文可以看出,異步函數(shù)實(shí)際上很快就調(diào)用完成了。但是后面還有工作線程執(zhí)行異步任務(wù)、通知主線程、主線程調(diào)用回調(diào)函數(shù)等很多步驟。我們把整個(gè)過程叫做異步過程。異步函數(shù)的調(diào)用在整個(gè)異步過程中,只是一小部分。

總結(jié)一下,一個(gè)異步過程通常是這樣的:

主線程發(fā)起一個(gè)異步請(qǐng)求,相應(yīng)的工作線程接收請(qǐng)求并告知主線程已收到(異步函數(shù)返回);主線程可以繼續(xù)執(zhí)行后面的代碼,同時(shí)工作線程執(zhí)行異步任務(wù);工作線程完成工作后,通知主線程;主線程收到通知后,執(zhí)行一定的動(dòng)作(調(diào)用回調(diào)函數(shù))。 

異步函數(shù)通常具有以下的形式:

A(args..., callbackFn)

它可以叫做異步過程的發(fā)起函數(shù),或者叫做異步任務(wù)注冊(cè)函數(shù)。args是這個(gè)函數(shù)需要的參數(shù)。callbackFn也是這個(gè)函數(shù)的參數(shù),但是它比較特殊所以多帶帶列出來。

所以,從主線程的角度看,一個(gè)異步過程包括下面兩個(gè)要素:

發(fā)起函數(shù)(或叫注冊(cè)函數(shù))A

回調(diào)函數(shù) callbackFn 

它們都是在主線程上調(diào)用的,其中注冊(cè)函數(shù)用來發(fā)起異步過程,回調(diào)函數(shù)用來處理結(jié)果。

舉個(gè)具體的例子:

setTimeout(fn, 1000);

其中的 setTimeout 就是異步過程的發(fā)起函數(shù), fn 是回調(diào)函數(shù)。

注意:前面說的形式 A(args..., callbackFn) 只是一種抽象的表示,并不代表回調(diào)函數(shù)一定要作為發(fā)起函數(shù)的參數(shù),例如:

var xhr = new XMLHttpRequest();
xhr.onreadystatechange = xxx; // 添加回調(diào)函數(shù)
xhr.open("GET", url);
xhr.send(); // 發(fā)起函數(shù)

發(fā)起函數(shù)和回調(diào)函數(shù)就是分離的。

四. 消息隊(duì)列和事件循環(huán)

上文講到,異步過程中,工作線程在異步操作完成后需要通知主線程。那么這個(gè)通知機(jī)制是怎樣實(shí)現(xiàn)的呢?答案是利用消息隊(duì)列和事件循環(huán)。

用一句話概括:

工作線程將消息放到消息隊(duì)列,主線程通過事件循環(huán)過程去取消息。 

消息隊(duì)列:消息隊(duì)列是一個(gè)先進(jìn)先出的隊(duì)列,它里面存放著各種消息。

事件循環(huán):事件循環(huán)是指主線程重復(fù)從消息隊(duì)列中取消息、執(zhí)行的過程。

實(shí)際上,主線程只會(huì)做一件事情,就是從消息隊(duì)列里面取消息、執(zhí)行消息,再取消息、再執(zhí)行。當(dāng)消息隊(duì)列為空時(shí),就會(huì)等待直到消息隊(duì)列變成非空。而且主線程只有在將當(dāng)前的消息執(zhí)行完成后,才會(huì)去取下一個(gè)消息。這種機(jī)制就叫做事件循環(huán)機(jī)制,取一個(gè)消息并執(zhí)行的過程叫做一次循環(huán)。

事件循環(huán)用代碼表示大概是這樣的:

while(true) {
    var message = queue.get();
    execute(message);
}

那么,消息隊(duì)列中放的消息具體是什么東西?消息的具體結(jié)構(gòu)當(dāng)然跟具體的實(shí)現(xiàn)有關(guān),但是為了簡(jiǎn)單起見,我們可以認(rèn)為:

消息就是注冊(cè)異步任務(wù)時(shí)添加的回調(diào)函數(shù)。 

再次以異步AJAX為例,假設(shè)存在如下的代碼:

$.ajax("http://segmentfault.com", function(resp) {
    console.log("我是響應(yīng):", resp);
});
 
// 其他代碼
...
...
...

主線程在發(fā)起AJAX請(qǐng)求后,會(huì)繼續(xù)執(zhí)行其他代碼。AJAX線程負(fù)責(zé)請(qǐng)求segmentfault.com,拿到響應(yīng)后,它會(huì)把響應(yīng)封裝成一個(gè)JavaScript對(duì)象,然后構(gòu)造一條消息:

// 消息隊(duì)列中的消息就長(zhǎng)這個(gè)樣子
var message = function () {
    callbackFn(response);
}

其中的 callbackFn 就是前面代碼中得到成功響應(yīng)時(shí)的回調(diào)函數(shù)。

主線程在執(zhí)行完當(dāng)前循環(huán)中的所有代碼后,就會(huì)到消息隊(duì)列取出這條消息(也就是 message 函數(shù)),并執(zhí)行它。到此為止,就完成了工作線程對(duì)主線程的通知,回調(diào)函數(shù)也就得到了執(zhí)行。如果一開始主線程就沒有提供回調(diào)函數(shù),AJAX線程在收到HTTP響應(yīng)后,也就沒必要通知主線程,從而也沒必要往消息隊(duì)列放消息。

用圖表示這個(gè)過程就是:

從上文中我們也可以得到這樣一個(gè)明顯的結(jié)論,就是:

異步過程的回調(diào)函數(shù),一定不在當(dāng)前這一輪事件循環(huán)中執(zhí)行。 

五. 異步與事件

上文中說的“事件循環(huán)”,為什么里面有個(gè)事件呢?那是因?yàn)椋?/p>

消息隊(duì)列中的每條消息實(shí)際上都對(duì)應(yīng)著一個(gè)事件。 

上文中一直沒有提到一類很重要的異步過程:DOM事件。

舉例來說:

var button = document.getElement("#btn");
button.addEventListener("click", function(e) {
    console.log();
});

從事件的角度來看,上述代碼表示:在按鈕上添加了一個(gè)鼠標(biāo)單擊事件的事件監(jiān)聽器;當(dāng)用戶點(diǎn)擊按鈕時(shí),鼠標(biāo)單擊事件觸發(fā),事件監(jiān)聽器函數(shù)被調(diào)用。

從異步過程的角度看, addEventListener 函數(shù)就是異步過程的發(fā)起函數(shù),事件監(jiān)聽器函數(shù)就是異步過程的回調(diào)函數(shù)。事件觸發(fā)時(shí),表示異步任務(wù)完成,會(huì)將事件監(jiān)聽器函數(shù)封裝成一條消息放到消息隊(duì)列中,等待主線程執(zhí)行。

事件的概念實(shí)際上并不是必須的,事件機(jī)制實(shí)際上就是異步過程的通知機(jī)制。我覺得它的存在是為了編程接口對(duì)開發(fā)者更友好。

另一方面,所有的異步過程也都可以用事件來描述。例如:setTimeout可以看成對(duì)應(yīng)一個(gè)時(shí)間到了的事件。前文的 setTimeout(fn, 1000); 可以看成:

timer.addEventListener("timeout", 1000, fn);

六. 生產(chǎn)者與消費(fèi)者

從生產(chǎn)者與消費(fèi)者的角度看,異步過程是這樣的:

工作線程是生產(chǎn)者,主線程是消費(fèi)者(只有一個(gè)消費(fèi)者)。工作線程執(zhí)行異步任務(wù),執(zhí)行完成后把對(duì)應(yīng)的回調(diào)函數(shù)封裝成一條消息放到消息隊(duì)列中;主線程不斷地從消息隊(duì)列中取消息并執(zhí)行,當(dāng)消息隊(duì)列空時(shí)主線程阻塞,直到消息隊(duì)列再次非空。 

PS:ECMAScript 262規(guī)范中,并沒有對(duì)異步、事件隊(duì)列等概念及其實(shí)現(xiàn)的描述。這些都是具體的JavaScript運(yùn)行時(shí)環(huán)境使用的機(jī)制。本文重點(diǎn)是描述異步過程的原理,為了便于理解做了很多簡(jiǎn)化。所以文中的某些術(shù)語的使用可能是不準(zhǔn)確的,具體細(xì)節(jié)也未必是正確的,例如消息隊(duì)列中消息的結(jié)構(gòu)。請(qǐng)讀者注意。

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://www.ezyhdfw.cn/yun/80701.html

相關(guān)文章

  • 轉(zhuǎn)】深入理解JS單線程機(jī)制【原文作者:MasterYao】

    摘要:的單線程,與它的用途有關(guān)。只要指定過回調(diào)函數(shù),這些事件發(fā)生時(shí)就會(huì)進(jìn)入任務(wù)隊(duì)列,等待主線程讀取。四主線程從任務(wù)隊(duì)列中讀取事件,這個(gè)過程是循環(huán)不斷的,所以整個(gè)的這種運(yùn)行機(jī)制又稱為事件循環(huán)。令人困惑的是,文檔中稱,指定的回調(diào)函數(shù),總是排在前面。 原文:http://www.cnblogs.com/Master... 一、為什么JavaScript是單線程? JavaScript語言的一大特點(diǎn)...

    LittleLiByte 評(píng)論0 收藏0
  • JavaScript同步異步事件循環(huán)

    摘要:同步異步是單線程的,每次只能做一件事情。像以下這種情況,代碼會(huì)按順序執(zhí)行,這個(gè)就叫同步。雖然是單線程,但是瀏覽器是多線程的,在遇到像事件等這種任務(wù)時(shí),會(huì)轉(zhuǎn)交給瀏覽器的其他工作線程上面提到的幾個(gè)線程執(zhí)行,執(zhí)行完之后將回調(diào)函數(shù)放入到任務(wù)隊(duì)列。 同步、異步 JS是單線程的,每次只能做一件事情。像以下這種情況,代碼會(huì)按順序執(zhí)行,這個(gè)就叫同步。 console.log(1); console.l...

    zr_hebo 評(píng)論0 收藏0
  • Node_深入淺出Node

    摘要:簡(jiǎn)介項(xiàng)目命名為就是一個(gè)服務(wù)器單純開發(fā)一個(gè)服務(wù)器的想法,變成構(gòu)建網(wǎng)絡(luò)應(yīng)用的一個(gè)基本框架發(fā)展為一個(gè)強(qiáng)制不共享任何資源的單線程,單進(jìn)程系統(tǒng)。單線程弱點(diǎn)無法利用多核錯(cuò)誤會(huì)引起整個(gè)應(yīng)用退出,應(yīng)用的健壯性大量計(jì)算占用導(dǎo)致無法繼續(xù)調(diào)用異步。 NodeJs簡(jiǎn)介 Ryan Dahl項(xiàng)目命名為:web.js 就是一個(gè)Web服務(wù)器.單純開發(fā)一個(gè)Web服務(wù)器的想法,變成構(gòu)建網(wǎng)絡(luò)應(yīng)用的一個(gè)基本框架.Node發(fā)展...

    shinezejian 評(píng)論0 收藏0
  • 前端面試:js同步異步問題

    摘要:今天的已經(jīng)成為一門功能全面的編程語言總結(jié)最初的用途是為來實(shí)現(xiàn)用戶與瀏覽器的交互二為何是單線程的的單線程,與它的用途有關(guān)。這決定了它只能是單線程,否則會(huì)帶來很復(fù)雜的同步問題。 showImg(https://user-gold-cdn.xitu.io/2019/3/31/169d1c40c27a173c?w=428&h=252&f=png&s=35393); 前言 我本來是打算寫一篇co...

    call_me_R 評(píng)論0 收藏0
  • Node.js 異步異聞錄

    摘要:的異步完成整個(gè)異步環(huán)節(jié)的有事件循環(huán)觀察者請(qǐng)求對(duì)象以及線程池。執(zhí)行回調(diào)組裝好請(qǐng)求對(duì)象送入線程池等待執(zhí)行,實(shí)際上是完成了異步的第一部分,回調(diào)通知是第二部分。異步編程是首個(gè)將異步大規(guī)模帶到應(yīng)用層面的平臺(tái)。 showImg(https://segmentfault.com/img/remote/1460000011303472); 本文首發(fā)在個(gè)人博客:http://muyunyun.cn/po...

    zzbo 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<