關於這一章我們會花時間定義一下 JS 的同步和非同步,簡介一下 JS 這語言的特性與跟瀏覽器的關係。雖然可能會覺得主題有點跳,但是像是 JS 相關的渲染效果或是執行順序,以及下兩章節要講的 Promise 跟 async/await都與它習習相關。
Javascript 這語言本身就設計為單線程,因為作為瀏覽器的腳本而言,它主要負責的頁面的互動,以及Dom的操作,所以它只能是單線程,否則會導致很多問題,例如:一個線程要進行渲染特效,但另一個線程卻要刪除這個節點,瀏覽器這時候該怎麼反應?
所以為了避免這種問題,Javascript 就是單線程,而這裡指的單線程是說 Javascript 解析跟執行代碼的線程只有一個,而瀏覽器是多線程的與 Javascript 單線程並不衝突,因為瀏覽器只是 Javascript 的運行環境。
可以將工作管理員打開,這時後你會看到許多執行應用程式,每個應用程式或許命名為 ****.exe。是的,可以把每一個 ****.exe 都當作一個進程來看待。
那 ****.exe 中可能會做很多事,像是瀏覽器,當你點擊它的時候,它幫你開網頁;當你不知不覺,它就幫你更新好了你有安裝的相關套件的最新版,可以把它們各自都當作是一個線程來看待。
進程擁有自己的系統資源,而且互相不影響,所以如果一個進程 crash 掉並不會對其他的進程造成影響。線程則是由進程堆疊起來的,所以當線程 crash 掉了,所有的進程也會跟著 crash。
也因此多進程的程序是比多線程的程序健康的,但是進程在切換時,耗費資源會較多,效率會比較差。
所謂的同步,就是做完一件事才能做下一件事
let num = [];
for(let i = 0;i < 100;i++){
num[i] = i;
}
console.log(num);
上面這段代碼是由上到下依順序執行,最後才會輸出,而這就是同步,事實上大部分程式還是同步多。
在做一件事情時,因為這件事會花費很長時間,所以在做這件事的同時,你還可以去處理其他事情。
由於瀏覽器是多線程的,但解析 Javascript 的程式卻是單線程的,所以有些任務需要耗費時間(像是 ajax, loading),如果按照同步的方式就會阻塞。所以單線程中有一些任務需要耗費時間,就把這些事情通過新開的線程來實現,而瀏覽器會針對那些耗時間的任務,會開一個新的進程單獨去處理。
說到這裡,所以 Javascript 的單線程又是如何實現異步?就是依據上圖的事件循環 (event loop) 實現。
舉個例子,可以猜猜下面的解是多少?
console.log('1');
setTimeout(function(){
console.log('2');
},0);
console.log('3');
// 1,3,2
setTimeout 會在堆疊中添加一個元素,而 0
作為第二個參數被傳入,如果堆疊中沒有其他元素,元素會被立刻處理。但是,如果主線程有其他的元素,setTimeout必須等其他元素處理完,因此第二個參數僅僅表示最少的時間,而不是確切的時間。
同步任務可以保證順序一致性,代碼也比較好理解,但卻容易造成阻塞;異步任務可以解決阻塞的問題,但卻會改變了順序,所以需要根據需求去撰寫程式。
後續我們會介紹 Javascript 處理異步的方式,ES6 的 Promise 以及 ES7 的 async/await。