基本概念
JS單線程:我們都知道JavaScript它是一個單線程的語言,同一時間只能做一件事。比如:在瀏覽器中,某一時刻我們在操作DOM,你們這個時刻我們就不能去運行JavaScript代碼,反過來也是,當我們在運行JavaScript代碼的時候,我們也不能去操作DOM,這個也就是JS的單線程。為什么要JS成單線程?因為在瀏覽器環境下,如果是多線程的,也就是操作DOM和運行JavaScript代碼是并行處理的話,假如某個時刻,瀏覽器正在繪制DOM,但是這個時候的JavaScript代碼改變了DOM,這樣就會造成一個不一致,因此JS就被設計成了一門單線程語言。
雖然JS是單線程,但是它的宿主,我們的瀏覽器環境(node環境)它是一個多線程的,在瀏覽器和node里面只有一條JS線程,但是還有很多其他的線程。以瀏覽器為例,瀏覽器的常駐線程(如下)大概有前三個:第一個,UI線程,也就是DOM瀏覽器元素的回流和重繪,這個UI線程與JavaScript線程是互斥的。第二個就是JavaScript線程,單線程的運行JS代碼。第三個是GUI線程,它主要是處理與用戶交互的一些邏輯,比如點擊某一個元素,拖動了或者縮放了這個就是由GUI線程處理的。除此之外還有NetWorker線程,網絡線程,發送ajax請求,發送http請求都是走的network線程。還有File線程,讀取文件。還有一個定時器線程。
- UI線程 - 回流和重繪 - 與Javascript線程互斥
- JavaScript線程 - 單線程運行JavaScript
- GUI線程 - 交互線程
- Network線程
- File線程
- 定時器線程
這里可能會有一個問題是ajax請求對于JS來說是異步的,但是JavaScript是單線程的,這樣就涉及到了一個基于事件的驅動。當我們發起一個網絡請求時,瀏覽器會把這一部分網絡請求交給network線程去處理,然后JavaScript線程等待network的指令驅動。在沒有代碼要運行的情況下,JavaScript線程始終是空閑的,有了事件驅動之后,它會一直處于一個輪詢的狀態(Event Loop),瀏覽器會不斷查詢目前是否有JavaScript線程需要運行,如果有就運行,沒有就保持閑置。當一個網絡請求發送出去,這個時候的JavaScript線程是處于閑置的,但是瀏覽器還是會不停的詢問,當network線程結束后,瀏覽器發現有新的JavaScript代碼需要執行,它就會驅動JavaScript的線程去處理網絡請求返回的結果,這個就是JS 基于事件驅動的模型。
Event Loop(事件輪詢圖)
異步是將耗時比較長的任務放置到Event Queue 事件隊列的尾部。
WebWorker
WebWorker為了解決瀏覽器假死這個問題而孕育而生的一項新技術。它是多線程模型,也是基于宿主。它屬于JavaScript線程中的一個子線程,它完全受主線程控制,但是在WebWorker里面是不能操作DOM的。因為上面提到的UI線程和JavaScript線程是互斥的,這個互斥也就保證了DOM的唯一性,因此主的基調不能改變,但是需要有一個新的線程來分擔繁雜的計算任務,這個也就是WebWorker。
瀏覽器的兼容性
應用場景
WebWorker是為了處理影響UI線程的JavaScript運算。因為在同一時刻,UI線程和JavaScript線程只能有一個在運行,如果這個時候JS的線程承擔過多運算的話,它的耗時就變得很長,這個時候的UI線程是沒有反應的,這樣就造成了頁面的假死。
WebWorker特點
- 一旦新建就會始終運行,不會被主線程打斷。即使主線程卡死了,WebWorker依然在運行。
- 同源限制,對于同一個WebWorker來講,只有同源的網頁才能夠訪問。
- 不能操作和訪問DOM(window、document),因為要保證DOM 的唯一性。
- 不能使用包含交互的全局方法(alert、confirm),但是可以使用XMLHttpRequest、setTimeout、setInterval
- 不能讀取本地文件(不止WebWorker不能讀取,JavaScript主線程也不能讀取),出于安全性的考慮,瀏覽器是不允許js讀取本地文件的。
- WebWorker分為兩個:dedicated web worker(專用線程) ,只有一個網址一個頁面可以使用這個線程和 shared web worker(共享線程),多個同源的網頁可以共享一個WebWorker,這樣為跨頁面通信提供了一種可能。
基本用法
- 創建WebWorker
const webWorker = new Worker('main.js'); let result = 0; const fibonacci = (n) => { if (n <= 1) return 1; return fibonacci(n - 1) + fibonacci(n - 2); } result = fibonacci(10); console.log('result', result);
- 向WebWorker發送消息(數據)
webWorker.postMessage({ number : 10 });
- WebWorker接收消息
webWorker.addEventListener('message', event => { console.log('received webworker data', event.data); }, false);
- WebWorker發送消息(返回數據)
this.postMessage(returnValue);
- 主線程接收WebWorker消息
webWorker.addEventListener('message', event => { console.log('received webworker data', event.data); }, false);
- 關閉WebWorker
方式一:在主線程關閉WebWorker webWorker.terminate(); 方式二:在子線程,WebWorker內部自己調用自己的close方法,不再接收新的 Macrotask(宏任務) this.close();
WebWorker調用腳本
importScripts('./one.js', './two.js');
WebWorker錯誤監聽
webWorker.addEventListener('error', error => {
console.error(error.filename, error.lineno, error.message);
});