非同步/回撥
單執行緒的JavaScript
說起非同步,就要先說說JavaScript執行機制。我們知道,JavaScript是單執行緒執行的,意味著同一個時間點,只有一個任務在執行。單執行緒就意味著,所有任務需要排隊,前一個任務結束,才會執行後一個任務。如果前一個任務耗時很長,後一個任務就不得不一直等著。
從誕生起,JavaScript就是單執行緒,這已經成了這門語言的核心特徵,將來也不會改變。
為什麼需要非同步?
單執行緒的好處是實現起來比較簡單,執行環境相對單純;壞處是隻要有一個任務耗時很長,後面的任務都必須排隊等著,會拖延整個程式的執行。常見的瀏覽器無響應(假死),往往就是因為某一段Javascript程式碼長時間執行(比如死迴圈),導致整個頁面卡在這個地方,其他任務無法執行。
為了解決這個問題,Javascript語言將任務的執行模式分成兩種:同步(Synchronous)和非同步(Asynchronous)。
關於非同步處理,現在已經有了許多好的解決方案。如果想全面瞭解如何處理非同步,@肖雞已經寫了一篇非常全面的文章JS中非同步的解決方案。
JavaScript執行機制
在談非同步之前,先來說說JavaScript的執行機制,看下面這段程式碼
function foo () {
return foo();
}
foo();
// Uncaught RangeError: Maximum call stack size exceeded
這程式碼裡面丟擲了一個錯誤,意思是超過最大呼叫堆疊大小,那麼這個call stack是什麼呢?
call stack:執行JavaScript的主執行緒分為heap和stack,stack是一個執行環境上下文。
stack是一種資料結構,資料先入後出,後入先出。執行JavaScript的call stack,也是如此。
從上圖的例子可以看出呼叫棧的變化
main(js檔案可以視作一個main函式) -> printSquare(內部呼叫了square,因此需要把square推入棧中) -> square(內部呼叫了multiply,推入棧中) -> multiply
此時所依賴的函式都在棧中,那麼可以執行了,執行順序和棧是一致的,後入先出(執行),所以順序為
multiply -> square -> printSquare。
call stack也是有最大限制的,可以使用下面的程式碼測試一下瀏覽器的最大call stack size
var i = 0;
function inc() {
i++;
inc();
}
inc();
//VM202:2 Uncaught RangeError: Maximum call stack size exceeded
i // 15720
理解JavaScript的函式呼叫方式,對於理解遞迴,高階函式,非同步函式等都是非常有幫助的。
以遞迴為例,遞迴函式不斷呼叫自身,那麼就會不斷向call stack中推入函式,直到達到遞迴條件(此時函式不再呼叫自身),然後再按後進先出的原則依次執行stack中的函式。
非同步的實現
非同步的實現我分為三部分來理解:webApi,任務佇列,event loop
webApi
先來列舉一下JavaScript中的非同步任務,現在先限定在瀏覽器中,可以得出以下結果:
dom事件
定時器setTimeout,setInterval
XMLHttpRequest
可以發現,這些都是都是瀏覽器的一些api,也就是webApi。其實非同步的實現是瀏覽器來處理的,主執行緒並不用管非同步時如何實現的。
事實上,瀏覽器是多程式的,所以可以開多個執行緒來處理非同步行為,並在任務完成時同步到任務佇列
任務佇列
看下面這段程式碼,setTimeout指定的函式0ms後輸出,但是最後才執行
console.log(1);
setTimeout(() => {console.log('after 0ms')} ,0);
console.log(2);
console.log(3);
// 1
// 2
// 3
// after 0ms
因為setTimeout的函式經過webApi,0ms後定時器執行並將回撥函式放到task queue,當call stack中的程式碼執行完畢時,主程式不斷檢視task queue中的任務,如果有任務就取出並放到call stack中執行。
setTimeout的定時是不準確的,因為當前call stack執行任務時,定時器的回撥就會一直在task queue中等待
對於其他的非同步api,如dom事件,ajax請求等,都是同樣的原理,當非同步事件執行完畢,就會把相應的回撥函式放到task queue中。
task queue中的任務需要反覆輪詢,檢視是否有任務已完成,這個輪詢就是event loop
Event loop
event loop經常用類似如下的方式來實現
while (queue.waitForMessage()) {
queue.processNextMessage();
}
如果當前沒有任何訊息queue.waitForMessage 會等待同步訊息到達。
非同步和回撥的關係
說到現在,非同步和回撥的關係已經很明確了。
非同步:通過webApi建立非同步任務。任務完成時,如果有指定了回撥函式,將回撥函式放入task queue中;如果沒有指定回撥函式,這個事件就被丟棄。
回撥函式:定義了非同步任務完成時所要執行的操作,包括事件和定時器所指定的非同步任務。
避免同步阻塞的程式碼
像深度迴圈,同步的ajax請求等任務會非常耗時,主執行緒有程式碼執行時,task queue中的程式碼就會一直處於等待狀態,此時瀏覽器無法進行任何互動和操作,頁面就相當於掛掉了。
相關文章
- C++屌屌的觀察者模式-同步回撥和非同步回撥C++模式非同步
- java回撥函式-非同步回撥-簡明講解Java函式非同步
- javascript非同步回撥是什麼JavaScript非同步
- 解析Promise解決非同步回撥Promise非同步
- 【設計模式】非同步阻塞、非同步回撥模式設計模式非同步
- Javascript回撥非同步操作示例教程JavaScript非同步
- ActiveMQ-MessageListener非同步回撥處理MQ非同步
- C# 同步 非同步 回撥 狀態機 async await DemoC#非同步AI
- 回撥地獄-編寫非同步JavaScript指南非同步JavaScript
- 用匿名內部類實現 Java 同步回撥Java
- arcgis api for js回撥函式如何等待同步APIJS函式
- JS 非同步發展流程(回撥函式=>Async/await)JS非同步函式AI
- Swift 中如何利用閉包實現非同步回撥?Swift非同步
- [JS]回撥函式和回撥地獄JS函式
- 回撥方法
- jQuery原始碼剖析(四) - Deferred非同步回撥解決方案jQuery原始碼非同步
- 基於Guava API實現非同步通知和事件回撥GuavaAPI非同步事件
- JavaScript 非同步操作裡的巢狀回撥函式JavaScript非同步巢狀函式
- Activity生命週期回撥是如何被回撥的?
- js 回撥 callbackJS
- 回撥函式函式
- 回撥地獄
- C++回撥C++
- 支付寶PC端單筆支付同步回撥session失效問題Session
- 併發程式設計 —— 自己寫一個非同步回撥 API程式設計非同步API
- Android和iOS開發中的非同步處理(二)——非同步任務的回撥AndroidiOS非同步
- 《Node.js設計模式》基於回撥的非同步控制流Node.js設計模式非同步
- 什麼是 JavaScript 裡的非同步操作和回撥函式JavaScript非同步函式
- JavaScript 回撥函式JavaScript函式
- 微博回撥介面
- JavaScript回撥函式JavaScript函式
- JS—回撥函式JS函式
- 回撥函式(CallBack)函式
- 【詳細、開箱即用】.NET企業微信回撥配置(資料回撥URL和指令回撥URL驗證)
- 【譯】非同步JavaScript的演變史:從回撥到Promises再到Async/Await非同步JavaScriptPromiseAI
- JavaScript非同步程式設計史:回撥函式到Promise到Async/AwaitJavaScript非同步程式設計函式PromiseAI
- ajax--實現非同步請求,接受響應及執行回撥非同步
- 如何避免回撥地獄