• <tfoot id="ukgsw"><input id="ukgsw"></input></tfoot>
    
    • 久久精品精选,精品九九视频,www久久只有这里有精品,亚洲熟女乱色综合一区
      分享

      JavaScript:同步、異步和事件循環

       torony 2016-04-13

      一. 單線程

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

      所謂單線程,是指在JS引擎中負責解釋和執行JavaScript代碼的線程只有一個。不妨叫它主線程。

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

      二. 同步和異步

      假設存在一個函數A:

      A(args...);

      同步:如果在函數A返回的時候,調用者就能夠得到預期結果(即拿到了預期的返回值或者看到了預期的效果),那么這個函數就是同步的。

      例如:

      Math.sqrt(2);
      
      console.log('Hi');
      • 第一個函數返回時,就拿到了預期的返回值:2的平方根。

      • 第二個函數返回時,就看到了預期的效果:在控制臺打印了一個字符串。

      所以這兩個函數都是同步的。

      異步:如果在函數A返回的時候,調用者還不能夠得到預期結果,而是需要在將來通過一定的手段得到,那么這個函數就是異步的。

      例如:

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

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

      但是在fs.readFile函數返回時,我們期望的結果并不會發生,而是要等到文件全部讀取完成之后。如果文件很大的話可能要很長時間。

      下面以AJAX請求為例,來看一下同步和異步的區別:

      >> 異步AJAX:

      主線程:“你好,AJAX線程。請你幫我發個HTTP請求吧,我把請求地址和參數都給你了。”

      AJAX線程:“好的,主線程。我馬上去發,但可能要花點兒時間呢,你可以先去忙別的。”

      主線程::“謝謝,你拿到響應后告訴我一聲啊。”

      (接著,主線程做其他事情去了。一頓飯的時間后,它收到了響應到達的通知。)

      >> 同步AJAX:

      主線程 :“你好,AJAX線程。請你幫我發個HTTP請求吧,我把請求地址和參數都給你了。”

      AJAX線程:“......”

      主線程::“喂,AJAX線程,你怎么不說話?”

      AJAX線程:“......”

      主線程:“喂!喂喂喂!”

      AJAX線程:“......”

      (一炷香的時間后)

      主線程::“喂!求你說句話吧!”

      AJAX線程:“主線程,不好意思,我在工作的時候不能說話。你的請求已經發完了,拿到響應數據了,給你。”

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

      三. 異步過程的構成要素

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

      總結一下,一個異步過程通常是這樣的:

      主線程發起一個異步請求,相應的工作線程接收請求并告知主線程已收到(異步函數返回);主線程可以繼續執行后面的代碼,同時工作線程執行異步任務;工作線程完成工作后,通知主線程;主線程收到通知后,執行一定的動作(調用回調函數)。

      異步函數通常具有以下的形式:

      A(args..., callbackFn)

      它可以叫做異步過程的發起函數,或者叫做異步任務注冊函數。 args 是這個函數需要的參數。 callbackFn 也是這個函數的參數,但是它比較特殊所以單獨列出來。

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

      • 發起函數(或叫注冊函數)A

      • 回調函數 callbackFn

      它們都是在主線程上調用的,其中注冊函數用來發起異步過程,回調函數用來處理結果。

      舉個具體的例子:

      setTimeout(fn, 1000);

      其中的  setTimeout 就是異步過程的發起函數,  fn   是回調函數。

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

      var xhr = new XMLHttpRequest();
      xhr.onreadystatechange = xxx; // 添加回調函數
      xhr.open('GET', url);
      xhr.send(); // 發起函數

      發起函數和回調函數就是分離的。

      四. 消息隊列和事件循環

      上文講到,異步過程中,工作線程在異步操作完成后需要通知主線程。那么這個通知機制是怎樣實現的呢?答案是利用消息隊列和事件循環。

      用一句話概括:

      工作線程將消息放到消息隊列,主線程通過事件循環過程去取消息。

      • 消息隊列:消息隊列是一個先進先出的隊列,它里面存放著各種消息。

      • 事件循環:事件循環是指主線程重復從消息隊列中取消息、執行的過程。

      實際上,主線程只會做一件事情,就是從消息隊列里面取消息、執行消息,再取消息、再執行。當消息隊列為空時,就會等待直到消息隊列變成非空。而且主線程只有在將當前的消息執行完成后,才會去取下一個消息。這種機制就叫做事件循環機制,取一個消息并執行的過程叫做一次循環。

      事件循環用代碼表示大概是這樣的:

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

      那么,消息隊列中放的消息具體是什么東西?消息的具體結構當然跟具體的實現有關,但是為了簡單起見,我們可以認為:

      消息就是注冊異步任務時添加的回調函數。

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

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

      主線程在發起AJAX請求后,會繼續執行其他代碼。AJAX線程負責請求segmentfault.com,拿到響應后,它會把響應封裝成一個JavaScript對象,然后構造一條消息:

      // 消息隊列中的消息就長這個樣子
      var message = function () {
          callbackFn(response);
      }

      其中的  callbackFn 就是前面代碼中得到成功響應時的回調函數。

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

      用圖表示這個過程就是:

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

      異步過程的回調函數,一定不在當前這一輪事件循環中執行。

      五. 異步與事件

      上文中說的“事件循環”,為什么里面有個 事件 呢?那是因為:

      消息隊列中的每條消息實際上都對應著一個事件。

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

      舉例來說:

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

      從事件的角度來看,上述代碼表示:在按鈕上添加了一個鼠標單擊事件的事件監聽器;當用戶點擊按鈕時,鼠標單擊事件觸發,事件監聽器函數被調用。

      從異步過程的角度看, addEventListener 函數就是異步過程的發起函數,事件監聽器函數就是異步過程的回調函數。事件觸發時,表示異步任務完成,會將事件監聽器函數封裝成一條消息放到消息隊列中,等待主線程執行。

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

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

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

      六. 生產者與消費者

      從生產者與消費者的角度看,異步過程是這樣的:

      工作線程是生產者,主線程是消費者(只有一個消費者)。工作線程執行異步任務,執行完成后把對應的回調函數封裝成一條消息放到消息隊列中;主線程不斷地從消息隊列中取消息并執行,當消息隊列空時主線程阻塞,直到消息隊列再次非空。

      PS:ECMAScript 262規范中,并沒有對異步、事件隊列等概念及其實現的描述。這些都是具體的JavaScript運行時環境使用的機制。本文重點是描述異步過程的原理,為了便于理解做了很多簡化。所以文中的某些術語的使用可能是不準確的,具體細節也未必是正確的,例如消息隊列中消息的結構。請讀者注意。

        本站是提供個人知識管理的網絡存儲空間,所有內容均由用戶發布,不代表本站觀點。請注意甄別內容中的聯系方式、誘導購買等信息,謹防詐騙。如發現有害或侵權內容,請點擊一鍵舉報。
        轉藏 分享 獻花(0

        0條評論

        發表

        請遵守用戶 評論公約

        類似文章 更多

        主站蜘蛛池模板: 人人妻人人澡人人爽人人精品电影 | 国产白嫩护士被弄高潮| 免费观看的AV毛片的网站| 亚韩精品中文字幕无码视频| 亚洲精品一区二区动漫| 国产欧美VA天堂在线观看视频| 四虎亚洲精品无码| 日韩精品亚洲专在线电影| 国产欧美日韩另类精彩视频| 日韩人妻精品中文字幕| 丰满人妻AV无码一区二区三区| 日韩加勒比一本无码精品| 又爽又黄又无遮挡的激情视频 | 久久精品丝袜高跟鞋| 华人在线亚洲欧美精品| 亚洲爆乳精品无码AAA片 | 日本高清中文字幕免费一区二区 | 91中文字幕一区在线| 国产乱码精品一区二区三区四川人 | 国产成人手机高清在线观看网站| 国产精品久久中文字幕| 少妇厨房愉情理9仑片视频| 玩弄放荡人妻少妇系列| 少妇愉情理伦片BD| av一区二区中文字幕| 自拍日韩亚洲一区在线| 桃花岛亚洲成在人线AV| 亚欧AV无码乱码在线观看性色| 女高中生强奷系列在线播放| 亚洲国产精品一区二区第一页| 激情97综合亚洲色婷婷五| 久久天天躁狠狠躁夜夜婷| 亚洲成AV人片在线观看麦芽| 免费无码无遮挡裸体视频在线观看| 91福利视频一区二区| 亚洲国产成人AV在线电影播放| 好大好深好猛好爽视频免费| 亚洲国产日韩一区三区| 欧美野外伦姧在线观看| 奇米影视7777狠狠狠狠色| 亚洲综合无码AV在线观看|