<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                合規國際互聯網加速 OSASE為企業客戶提供高速穩定SD-WAN國際加速解決方案。 廣告
                # 異步操作概述 ## 單線程模型 單線程模型指的是,JavaScript 只在一個線程上運行。也就是說,JavaScript 同時只能執行一個任務,其他任務都必須在后面排隊等待。 注意,JavaScript 只在一個線程上運行,不代表 JavaScript 引擎只有一個線程。事實上,JavaScript 引擎有多個線程,單個腳本只能在一個線程上運行(稱為主線程),其他線程都是在后臺配合。 JavaScript 之所以采用單線程,而不是多線程,跟歷史有關系。JavaScript 從誕生起就是單線程,原因是不想讓瀏覽器變得太復雜,因為多線程需要共享資源、且有可能修改彼此的運行結果,對于一種網頁腳本語言來說,這就太復雜了。如果 JavaScript 同時有兩個線程,一個線程在網頁 DOM 節點上添加內容,另一個線程刪除了這個節點,這時瀏覽器應該以哪個線程為準?是不是還要有鎖機制?所以,為了避免復雜性,JavaScript 一開始就是單線程,這已經成了這門語言的核心特征,將來也不會改變。 這種模式的好處是實現起來比較簡單,執行環境相對單純;壞處是只要有一個任務耗時很長,后面的任務都必須排隊等著,會拖延整個程序的執行。常見的瀏覽器無響應(假死),往往就是因為某一段 JavaScript 代碼長時間運行(比如死循環),導致整個頁面卡在這個地方,其他任務無法執行。JavaScript 語言本身并不慢,慢的是讀寫外部數據,比如等待 Ajax 請求返回結果。這個時候,如果對方服務器遲遲沒有響應,或者網絡不通暢,就會導致腳本的長時間停滯。 如果排隊是因為計算量大,CPU 忙不過來,倒也算了,但是很多時候 CPU 是閑著的,因為 IO 操作(輸入輸出)很慢(比如 Ajax 操作從網絡讀取數據),不得不等著結果出來,再往下執行。JavaScript 語言的設計者意識到,這時 CPU 完全可以不管 IO 操作,掛起處于等待中的任務,先運行排在后面的任務。等到 IO 操作返回了結果,再回過頭,把掛起的任務繼續執行下去。這種機制就是 JavaScript 內部采用的“事件循環”機制(Event Loop)。 單線程模型雖然對 JavaScript 構成了很大的限制,但也因此使它具備了其他語言不具備的優勢。如果用得好,JavaScript 程序是不會出現堵塞的,這就是為什么 Node 可以用很少的資源,應付大流量訪問的原因。 為了利用多核 CPU 的計算能力,HTML5 提出 Web Worker 標準,允許 JavaScript 腳本創建多個線程,但是子線程完全受主線程控制,且不得操作 DOM。所以,這個新標準并沒有改變 JavaScript 單線程的本質。 ## 同步任務和異步任務 程序里面所有的任務,可以分成兩類:同步任務(synchronous)和異步任務(asynchronous)。 同步任務是那些沒有被引擎掛起、在主線程上排隊執行的任務。只有前一個任務執行完畢,才能執行后一個任務。 異步任務是那些被引擎放在一邊,不進入主線程、而進入任務隊列的任務。只有引擎認為某個異步任務可以執行了(比如 Ajax 操作從服務器得到了結果),該任務(采用回調函數的形式)才會進入主線程執行。排在異步任務后面的代碼,不用等待異步任務結束會馬上運行,也就是說,異步任務不具有“堵塞”效應。 舉例來說,Ajax 操作可以當作同步任務處理,也可以當作異步任務處理,由開發者決定。如果是同步任務,主線程就等著 Ajax 操作返回結果,再往下執行;如果是異步任務,主線程在發出 Ajax 請求以后,就直接往下執行,等到 Ajax 操作有了結果,主線程再執行對應的回調函數。 ## 任務隊列和事件循環 JavaScript 運行時,除了一個正在運行的主線程,引擎還提供一個任務隊列(task queue),里面是各種需要當前程序處理的異步任務。(實際上,根據異步任務的類型,存在多個任務隊列。為了方便理解,這里假設只存在一個隊列。) 首先,主線程會去執行所有的同步任務。等到同步任務全部執行完,就會去看任務隊列里面的異步任務。如果滿足條件,那么異步任務就重新進入主線程開始執行,這時它就變成同步任務了。等到執行完,下一個異步任務再進入主線程開始執行。一旦任務隊列清空,程序就結束執行。 異步任務的寫法通常是回調函數。一旦異步任務重新進入主線程,就會執行對應的回調函數。如果一個異步任務沒有回調函數,就不會進入任務隊列,也就是說,不會重新進入主線程,因為沒有用回調函數指定下一步的操作。 JavaScript 引擎怎么知道異步任務有沒有結果,能不能進入主線程呢?答案就是引擎在不停地檢查,一遍又一遍,只要同步任務執行完了,引擎就會去檢查那些掛起來的異步任務,是不是可以進入主線程了。這種循環檢查的機制,就叫做事件循環(Event Loop)。[維基百科](http://en.wikipedia.org/wiki/Event_loop)的定義是:“事件循環是一個程序結構,用于等待和發送消息和事件(a programming construct that waits for and dispatches events or messages in a program)”。 ## 異步操作的模式 下面總結一下異步操作的幾種模式。 ### 回調函數 回調函數是異步操作最基本的方法。 下面是兩個函數`f1`和`f2`,編程的意圖是`f2`必須等到`f1`執行完成,才能執行。 ```javascript function f1() { // ... } function f2() { // ... } f1(); f2(); ``` 上面代碼的問題在于,如果`f1`是異步操作,`f2`會立即執行,不會等到`f1`結束再執行。 這時,可以考慮改寫`f1`,把`f2`寫成`f1`的回調函數。 ```javascript function f1(callback) { // ... callback(); } function f2() { // ... } f1(f2); ``` 回調函數的優點是簡單、容易理解和實現,缺點是不利于代碼的閱讀和維護,各個部分之間高度[耦合](http://en.wikipedia.org/wiki/Coupling_(computer_programming))(coupling),使得程序結構混亂、流程難以追蹤(尤其是多個回調函數嵌套的情況),而且每個任務只能指定一個回調函數。 ### 事件監聽 另一種思路是采用事件驅動模式。異步任務的執行不取決于代碼的順序,而取決于某個事件是否發生。 還是以`f1`和`f2`為例。首先,為`f1`綁定一個事件(這里采用的 jQuery 的[寫法](http://api.jquery.com/on/))。 ```javascript f1.on('done', f2); ``` 上面這行代碼的意思是,當`f1`發生`done`事件,就執行`f2`。然后,對`f1`進行改寫: ```javascript function f1() { setTimeout(function () { // ... f1.trigger('done'); }, 1000); } ``` 上面代碼中,`f1.trigger('done')`表示,執行完成后,立即觸發`done`事件,從而開始執行`f2`。 這種方法的優點是比較容易理解,可以綁定多個事件,每個事件可以指定多個回調函數,而且可以“[去耦合](http://en.wikipedia.org/wiki/Decoupling)”(decoupling),有利于實現模塊化。缺點是整個程序都要變成事件驅動型,運行流程會變得很不清晰。閱讀代碼的時候,很難看出主流程。 ### 發布/訂閱 事件完全可以理解成“信號”,如果存在一個“信號中心”,某個任務執行完成,就向信號中心“發布”(publish)一個信號,其他任務可以向信號中心“訂閱”(subscribe)這個信號,從而知道什么時候自己可以開始執行。這就叫做”[發布/訂閱模式](http://en.wikipedia.org/wiki/Publish-subscribe_pattern)”(publish-subscribe pattern),又稱“[觀察者模式](http://en.wikipedia.org/wiki/Observer_pattern)”(observer pattern)。 這個模式有多種[實現](http://msdn.microsoft.com/en-us/magazine/hh201955.aspx),下面采用的是 Ben Alman 的 [Tiny Pub/Sub](https://gist.github.com/661855),這是 jQuery 的一個插件。 首先,`f2`向信號中心`jQuery`訂閱`done`信號。 ```javascript jQuery.subscribe('done', f2); ``` 然后,`f1`進行如下改寫。 ```javascript function f1() { setTimeout(function () { // ... jQuery.publish('done'); }, 1000); } ``` 上面代碼中,`jQuery.publish('done')`的意思是,`f1`執行完成后,向信號中心`jQuery`發布`done`信號,從而引發`f2`的執行。 `f2`完成執行后,可以取消訂閱(unsubscribe)。 ```javascript jQuery.unsubscribe('done', f2); ``` 這種方法的性質與“事件監聽”類似,但是明顯優于后者。因為可以通過查看“消息中心”,了解存在多少信號、每個信號有多少訂閱者,從而監控程序的運行。 ## 異步操作的流程控制 如果有多個異步操作,就存在一個流程控制的問題:如何確定異步操作執行的順序,以及如何保證遵守這種順序。 ```javascript function async(arg, callback) { console.log('參數為 ' + arg +' , 1秒后返回結果'); setTimeout(function () { callback(arg * 2); }, 1000); } ``` 上面代碼的`async`函數是一個異步任務,非常耗時,每次執行需要1秒才能完成,然后再調用回調函數。 如果有六個這樣的異步任務,需要全部完成后,才能執行最后的`final`函數。請問應該如何安排操作流程? ```javascript function final(value) { console.log('完成: ', value); } async(1, function (value) { async(2, function (value) { async(3, function (value) { async(4, function (value) { async(5, function (value) { async(6, final); }); }); }); }); }); // 參數為 1 , 1秒后返回結果 // 參數為 2 , 1秒后返回結果 // 參數為 3 , 1秒后返回結果 // 參數為 4 , 1秒后返回結果 // 參數為 5 , 1秒后返回結果 // 參數為 6 , 1秒后返回結果 // 完成: 12 ``` 上面代碼中,六個回調函數的嵌套,不僅寫起來麻煩,容易出錯,而且難以維護。 ### 串行執行 我們可以編寫一個流程控制函數,讓它來控制異步任務,一個任務完成以后,再執行另一個。這就叫串行執行。 ```javascript var items = [ 1, 2, 3, 4, 5, 6 ]; var results = []; function async(arg, callback) { console.log('參數為 ' + arg +' , 1秒后返回結果'); setTimeout(function () { callback(arg * 2); }, 1000); } function final(value) { console.log('完成: ', value); } function series(item) { if(item) { async( item, function(result) { results.push(result); return series(items.shift()); }); } else { return final(results[results.length - 1]); } } series(items.shift()); ``` 上面代碼中,函數`series`就是串行函數,它會依次執行異步任務,所有任務都完成后,才會執行`final`函數。`items`數組保存每一個異步任務的參數,`results`數組保存每一個異步任務的運行結果。 注意,上面的寫法需要六秒,才能完成整個腳本。 ### 并行執行 流程控制函數也可以是并行執行,即所有異步任務同時執行,等到全部完成以后,才執行`final`函數。 ```javascript var items = [ 1, 2, 3, 4, 5, 6 ]; var results = []; function async(arg, callback) { console.log('參數為 ' + arg +' , 1秒后返回結果'); setTimeout(function () { callback(arg * 2); }, 1000); } function final(value) { console.log('完成: ', value); } items.forEach(function(item) { async(item, function(result){ results.push(result); if(results.length === items.length) { final(results[results.length - 1]); } }) }); ``` 上面代碼中,`forEach`方法會同時發起六個異步任務,等到它們全部完成以后,才會執行`final`函數。 相比而言,上面的寫法只要一秒,就能完成整個腳本。這就是說,并行執行的效率較高,比起串行執行一次只能執行一個任務,較為節約時間。但是問題在于如果并行的任務較多,很容易耗盡系統資源,拖慢運行速度。因此有了第三種流程控制方式。 ### 并行與串行的結合 所謂并行與串行的結合,就是設置一個門檻,每次最多只能并行執行`n`個異步任務,這樣就避免了過分占用系統資源。 ```javascript var items = [ 1, 2, 3, 4, 5, 6 ]; var results = []; var running = 0; var limit = 2; function async(arg, callback) { console.log('參數為 ' + arg +' , 1秒后返回結果'); setTimeout(function () { callback(arg * 2); }, 1000); } function final(value) { console.log('完成: ', value); } function launcher() { while(running < limit && items.length > 0) { var item = items.shift(); async(item, function(result) { results.push(result); running--; if(items.length > 0) { launcher(); } else if(running == 0) { final(results); } }); running++; } } launcher(); ``` 上面代碼中,最多只能同時運行兩個異步任務。變量`running`記錄當前正在運行的任務數,只要低于門檻值,就再啟動一個新的任務,如果等于`0`,就表示所有任務都執行完了,這時就執行`final`函數。 這段代碼需要三秒完成整個腳本,處在串行執行和并行執行之間。通過調節`limit`變量,達到效率和資源的最佳平衡。
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看