大家可能都知道,JS語言的執行環境是單線程的。
所謂單線程就是指一次只能完成一件任務,如果有多個任務,就必須排隊,等前面一個任務執行完成,再執行后面一個任務,依次進行。
好處:實現起來比較簡單,執行環境相對單純。
缺點:只要存在一個任務耗時很久,后面的任務都必須排隊等着,會拖延整個程序的執行。常見的瀏覽器無響應(假死)往往就是因為某一段JS代碼長時間運行(比如死循環),導致整個頁面卡在某個任務,其它任務無法執行。
那么為了解決這個問題。JS將任務的執行模式分為兩種:同步(synchronous) 和 異步(asynchronous)。
同步上面已經介紹過,即后一個任務等前一個任務結束后再執行,程序的執行順序與任務的排列順序是一致的、同步的。
異步模式則完全不同,每一個任務有一個多多個回調函數(callback),前一個任務結束后,不是執行后一個任務,而是執行回調函數,后一個任務則是不等前一個任務結束就執行,所以程序的執行順序與任務的排列順序不是一致的。
異步模式非常重要。在瀏覽器端,耗時很長的操作都應該是異步執行,避免瀏覽器失去響應,最好的列子就是ajax操作。
異步調用不會阻止代碼的順序執行,而是在將來的某一個時刻觸發設置好的邏輯,所以我們:
1.並不知道邏輯什么時候會被調用;
2.只能定義當觸發的時候邏輯是什么;
3.只能等待,同時可以去處理其他的邏輯;
setTimeout就是這樣的一個異步調用。
var a = 1; function f1(a){ console.log("I am in f1"); setTimeout(function(){a+=2;console.log("f1:" + a);},1000)} f1(a); console.log("out:" + a);
得到的結果如下圖所示:
I am in f1
out:1
f1:3
結果表明代碼執行過程中確實是先執行了f1函數,在執行console.log輸出后,setTimeout的回調函數在執行過程中被掛起,知道1s后才被調用,在等待執行setTImeout回調函數的時間里,JS選擇執行后面的任務,即執行了console.log("out:" + a);
實際上,異步函數,如setTimeout和setInterval,是被壓入了稱之為Event Loop的隊列。
Event Loop是一個回調函數隊列。當異步函數執行時,回調函數會被壓入這個隊列。JavaScript引擎直到異步函數執行完成后,才會開始處理事件循環。這意味着JavaScript代碼不是多線程的,即使表現的行為相似。事件循環是一個先進先出(FIFO)隊列,這說明回調是按照它們被加入隊列的順序執行的。JavaScript被 node選做為開發語言,就是因為寫這樣的代碼多么簡單啊。