深入理解 JavaScript 異步系列(1)——基礎


前言

2014年秋季寫完了《深入理解javascript原型和閉包系列》,已經幫助過很多人走出了 js 原型、作用域、閉包的困惑,至今仍能經常受到好評的留言。

很早之前我就總結了JS三座大山這個概念(雖然沒有到處宣揚),前兩座(原型、作用域)已經基本講明白,而第三座(異步)也應該做一個總結。

於是,2017年初春,我花費大約一周的業余時間來對 JS 異步做一個完整的總結,和各位同學共勉共進步!

 

原文地址:http://www.cnblogs.com/wangfupeng1988/p/6513070.html 未經同意禁止轉載!

第一部分,什么是異步

提醒:如果你是初學 js 的同學,尚未有太多項目經驗和基礎知識,請就此打住,不要看這篇教程

我思考問題、寫文章一般都不按討論出牌,別人寫過的東西我不會再照着抄一遍。因此,后面所有的內容,都是我看了許多資料之后,個人重新思考提煉總結出來的,這肯定不能算是初級教程。

如果你是已有 js 開發經驗,並了解異步的基礎知識,到這里來想深入了解一下Promise Generatorasync-awati,那就太好了,非常歡迎。

本節內容概述

  • JS 為何會有異步
  • 異步的實現原理是什么
  • 常用的異步操作有哪些

JS 為何會有異步

首先記住一句話 —— JS 是單線程的語言,所謂“單線程”就是一根筋,對於拿到的程序,一行一行的執行,上面的執行為完成,就傻傻的等着。例如

var i, t = Date.now()
for (i = 0; i < 100000000; i++) {
}
console.log(Date.now() - t)  // 250 (chrome瀏覽器)

上面的程序花費 250ms 的時間執行完成,執行過程中就會有卡頓,其他的事兒就先撂一邊不管了。

執行程序這樣沒有問題,但是對於 JS 最初使用的環境 ———— 瀏覽器客戶端 ———— 就不一樣了。因此在瀏覽器端運行的 js ,可能會有大量的網絡請求,而一個網絡資源啥時候返回,這個時間是不可預估的。這種情況也要傻傻的等着、卡頓着、啥都不做嗎?———— 那肯定不行。

因此,JS 對於這種場景就設計了異步 ———— 即,發起一個網絡請求,就先不管這邊了,先干其他事兒,網絡請求啥時候返回結果,到時候再說。這樣就能保證一個網頁的流程運行。

異步的實現原理

先看一段比較常見的代碼

var ajax = $.ajax({
    url: '/data/data1.json',
    success: function () {
        console.log('success')
    }
})

上面代碼中$.ajax()需要傳入兩個參數進去,urlsuccess,其中url是請求的路由,success是一個函數。這個函數傳遞過去不會立即執行,而是等着請求成功之后才能執行。對於這種傳遞過去不執行,等出來結果之后再執行的函數,叫做callback,即回調函數

再看一段更加能說明回調函數的 nodejs 代碼。和上面代碼基本一樣,唯一區別就是:上面代碼時網絡請求,而下面代碼時 IO 操作。

var fs = require('fs')
fs.readFile('data1.json', (err, data) => {
    console.log(data.toString())
})

從上面兩個 demo 看來,實現異步的最核心原理,就是將callback作為參數傳遞給異步執行函數,當有結果返回之后再觸發 callback執行,就是如此簡單!

常用的異步操作

開發中比較常用的異步操作有:

  • 網絡請求,如ajax http.get
  • IO 操作,如readFile readdir
  • 定時函數,如setTimeout setInterval

最后,請思考,事件綁定是不是也是異步操作?例如$btn.on('click', function() {...})。這個問題很有意思,我會再后面的章節經過分析之后給出答案,各位先自己想一下。

第二部分,異步和 event-loop

提到異步,就必須提 event-loop 。event-loop 中文翻譯叫做“事件輪詢”,它是能體現出單線程中異步操作是如何被執行的。

首先,強烈大家觀看一個歪果仁的視頻《what the hack is event loop,只有不到半個小時的時間,但是將的非常詳細。如果那個鏈接失效,訪問這里(密碼: xx9f)

其次,再結合阮一峰老師的《什么是event loop》一起看一下。將這兩個看完就基本了解 event loop 了

最后,event-loop 是一塊內容比較獨立的技術性知識,它是什么樣子就是什么樣子,講解起來可變通性非常小。因此,本節說一下我對 event-loop 的理解和體會

本節內容概述

  • 舉例說明
  • 核心概念
  • 思考兩個問題

舉例說明

給出一段簡單的 js 代碼,並用比較通俗、簡單的說法介紹一下執行過程。詳細過程還需各位去看視頻,因為我沒必要把半小時的視頻都寫到這里。

console.log('line 1')
setTimeout(console.log, 1000, 'line 2')
console.log('line 3')

以上一共三行代碼,該程序被執行的時候,會依次挨行執行

  • 第一步,執行第一行,將結果line 1打印出來
  • 第二步,執行第二行,注意此時會將這個操作暫時存儲到其他地方,因為setTimeout是一個異步執行操作。
  • 第三步,執行第三行,將結果line 3打印出出來
  • 第四步,等待最后一行程序(一共三行)都全部執行完了,然后立馬實時查看剛才暫存的異步操作有沒有。如果有可執行的,就立即拿到出來繼續執行。
  • 第五步,執行完畢之后,再實時查看暫存位置中是否還有未執行的異步回調。

以上只拿了setTimeout舉例子,但是對於網絡請求、IO操作、事件綁定道理都是一樣的。如果我講的簡單例子你還是看不懂,一定要去看文章最初提到的《what the hack is event loop》視頻,重要重要!!!

思考三個問題

第一題,以下代碼的輸出順序是什么

setTimeout(console.log, 0, 'a')
console.log('b')
console.log('c')

答案是b c a,有疑問的需要再去看上面的介紹或者那個視頻。

第二題,以下代碼中,最后輸出的結果是否是 500

var i, t = Date.now()
for (i = 0; i < 100000000; i++) {
}
function fn() {
    console.log(Date.now() - t)  // 輸出多少???
}
setTimeout(fn, 500)

答案是大於 500ms ,因為 for 函數需要花費一些時間,等 for 執行完之后再開始計算 500ms 之后執行 fn

第三題,事件綁定是不是異步操作?

這個問題大家根據 event-loop 的講解和視頻來思考,我們下一節再給出解答。

第三部分,事件綁定算不算異步?

如果你認真看了上一節的 event-loop 的,你會發現原來事件綁定和異步操作的實現機制是一樣的,那么事件綁定是不是就是異步操作呢?(聲明一下,這里說的事件綁定是如下代碼的形式)

$btn.on('click', function (e) {
    console.log('你點擊了按鈕')
})

PS:這個問題貌似沒有加過有人討論或者發起討論,但是當我了解了 event-loop 之后,我就發現這兩者有很大聯系,很早就像討論一下這個話題。不知道哪位同仁跟我有一樣的想法?

本節內容概述

  • 共同之處
  • 不同之處
  • 我的觀點

共同之處

從技術實現以及書寫方法上來講,他們是一樣的。例如事件綁定和 IO 操作的寫法基本相同

$btn.on('click', function (e) {
    console.log('你點擊了按鈕')
})
fs.readFile('data1.json', function (err, data) {
    // 獲取數據
})

最終執行的方式也基本一樣,都通過 evet-loop 執行。

不同之處

在我看來至少有兩處不同。

第一,event-loop 執行時,調用的源不一樣。異步操作是系統自動調用,無論是setTimeout時間到了還是$.ajax請求返回了,系統會自動調用。而事件綁定就需要用戶手動觸發

第二,從設計上來將,事件綁定有着明顯的“訂閱-發布”的設計模式,而異步操作卻沒有。

我的觀點

我個人看代碼比較偏重設計,一個東西是什么要看它是未什么而設計的。因此,我傾向於事件綁定不是異步操作。雖然它也是通過 event-loop 實現調用的,但是它的設計目錄卻和異步操作完全不一樣。

其實,事件綁定在 js 中扮演着非常重要的角色,各個地方都會用到事件綁定的形式。例如 web 頁面監控鼠標、鍵盤,以及 nodejs 中的 EventEmitter 應用非常廣泛(特別是涉及到數據流時)。而事件綁定被應用到非常廣泛,卻沒有發生像異步操作帶來的程序邏輯問題,反而大家用的非常開心————這又一個兩者不一樣的例證。

如果你覺得我的觀點有問題,也可以大膽提出自己的建議和意見,發表出來!說對說錯都無所謂,也不會扣你落戶積分,只要能自圓其說就是好的。

求打賞

如果你看完了,感覺還不錯,歡迎給我打賞 ———— 以激勵我更多輸出優質內容

最后,github地址是 https://github.com/wangfupeng1988/js-async-tutorial 歡迎 star 和 pr

-------

學習作者教程:《前端JS高級面試》《前端JS基礎面試題》《React.js模擬大眾點評webapp》《zepto設計與源碼分析》《json2.js源碼解讀


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM