JavaScript多執行緒-Web Worker
JS組成
ECMAScript
ECMAScript
規定了 JavaScript
指令碼的核心語法,如 資料型別、關鍵字、保留字、運算子、物件和語句等,它不屬於任何瀏覽器。
Document Object Model
文件物件模型( DOM
)將 web
頁面與到指令碼或程式語言連線起來。通常是指 JavaScript
,但將 HTML
、 SVG
或 XML
文件建模為物件並不是 JavaScript
語言的一部分。 DOM
模型用一個邏輯樹來表示一個文件,樹的每個分支的終點都是一個節點( node
),每個節點都包含著物件( objects
)。 DOM
的方法( methods
)讓你可以用特定方式操作這個樹,用這些方法你可以改變文件的結構、樣式或者內容。節點可以關聯上事件處理器,一旦某一事件被觸發了,那些事件處理器就會被執行。
Browser Object Model
瀏覽器物件模型( BOM
),是用於描述這種物件與物件之間層次關係的模型,瀏覽器物件模型提供了獨立於內容的、可以與瀏覽器視窗進行互動的物件結構。 BOM
由多個物件組成,其中代表瀏覽器視窗的 Window
物件是 BOM
的頂層物件,其他物件都是該物件的子物件.
執行緒與程序
程序( Process
)是系統資源分配和排程的單元。一個執行著的程式就對應了一個程序。一個程序包括了執行中的程式和程式所使用到的記憶體和系統資源。如果是單核 CPU
的話,在同一時間內,有且只有一個程序在執行。但是,單核 CPU
也能實現多工同時執行,比如你邊聽網易雲音樂的每日推薦歌曲,邊在網易有道雲筆記上寫博文。這算開了兩個程序(多程序),那執行的機制就是一會兒播放一下歌,一會兒響應一下你的打字,但由於 CPU
切換的速度很快,你根本感覺不到,以至於你認為這兩個程序是在同時執行的。程序之間是資源隔離的。
那執行緒( Thread
)是什麼?執行緒是程序下的執行者,一個程序至少會開啟一個執行緒(主執行緒),也可以開啟多個執行緒。比如網易雲音樂一邊播放音訊,一邊顯示歌詞。多程序的執行其實也就是通過程序中的執行緒來執行的。一個程序下的執行緒是可以共享資源的,所以在多執行緒的情況下,需要特別注意對臨界資源的訪問控制.
瀏覽器
目前最為流行的瀏覽器為:`Chrome,IE,Safari,FireFox,Opera.
- 渲染引擎執行緒:負責頁面的渲染
-
JS
引擎執行緒:負責JS
的解析和執行 - 定時觸發器執行緒:處理定時事件,比如
setTimeout
,setInterval
- 事件觸發執行緒:處理
DOM
事件 - 非同步
http
請求執行緒:處理http
請求
需要注意的是,渲染執行緒和 JS
引擎執行緒是不能同時進行的。渲染執行緒在執行任務的時候, JS
引擎執行緒會被掛起。因為 JS
可以操作DOM,若在渲染中 JS
處理了 DOM
,瀏覽器可能就懵逼了。
Web Worker
簡介
Web Worker
(工作執行緒) 是 HTML5
中提出的概念, Web Workers
使得一個 Web
應用程式可以在與主執行執行緒分離的後臺執行緒中執行一個指令碼操作。這樣做的好處是可以在一個單獨的執行緒中執行費時的處理任務,從而允許主(通常是 UI
)執行緒執行而不被阻塞/放慢.
Web Worker
可以分為一下幾類:
- 專用執行緒(
Dedicated Worker
)
專用執行緒僅能被建立它的指令碼所使用(一個專用執行緒對應一個主執行緒)
- 共享執行緒(
Shared Worker
)
共享執行緒能夠在不同的指令碼中使用(一個共享執行緒對應多個主執行緒)
- 服務工作執行緒(
Service Workers
)
註冊在指定源和路徑下的事件驅動 worker
, 可以控制關聯的頁面或者網站,攔截並修改訪問和資源請求,細粒度地快取資源.
-
Chrome Workers
一種僅適用於 firefox
的 worker
.
-
Aduio Workers
可以在網路 worker
上下文中直接完成指令碼化音訊處理
瀏覽器相容性
可以通過 caniuse 檢視相容性
-
Dedicated Worker
相容性
-
Shared Worker
相容性
使用場景
canvas
限制
DOM Window
workerType | 上下文 |
---|---|
Dedicated Worker | DedicatedWorkerGlobalScope |
Shared Worker | SharedWorkerGlobalScope |
建立執行緒
- 檢查瀏覽器是否支援
if (window.Worker) { // some code }
- 專用執行緒
@params {String} url 表示worker將執行的指令碼的URL,必須遵守同源策略 @params {Object} [options] 建立物件例項時設定的選項屬性的物件 @params {Object} [options.type] @params {Object} [options.name] @params {Object} [options.credentials] @returns 建立的worker const myWorker = new Worker(url[, options]);
- 示例
// main.js const myDedicatedWorker = new Worker('./dedicated_worker/worker.js', { name: 'dedicatedWorker' }); // worker.js console.log('success');
- 共享執行緒
@params {String} url 表示worker將執行的指令碼的URL,必須遵守同源策略 @params {Object} [options] 建立物件例項時設定的選項屬性的物件 @params {Object} [options.type] @params {Object} [options.name] @params {Object} [options.credentials] @returns 建立的worker const myWorker = new SharedWorker(url[, options]);
- 示例
// main.js const mySharedWorker = new SharedWorker('./shared_worker/worker.js', { name: 'sharedWorker' }); // worker.js console.log('success');
執行緒通訊
- 傳送資訊
@params {Object} message 傳遞的資料物件 @params {Object} [options] 一個可選的Transferable物件的陣列,用於傳遞所有權.如果一個物件的所有權被轉移,在傳送它的上下文中將變為不可用(中止),並且只有在它被髮送到的worker中可用。 myWorker.postMessage(message, transferList);
- 接收資訊
myWorker.onmessage = function(event) { const data = event.data; // 接收到的訊息資料 }
- 專用執行緒示例
// main.js const myWorker = new Worker('worker.js'); myWorker.postMessage([10, 20]); myWorker.onmessage = function (event) { console.log(event.data); } // worker.js onmessage(event) { console.log(event.data); postMessage(event.data[1] - event.data[0]); }
- 共享執行緒示例
// main.js const myWorker = new SharedWorker('worker.js'); myWorker.port.start(); myWorker.port.postMessage([10, 20]); myWorker.port.onmessage = function (event) { console.log(event.data); } // worker.js connect(event) { const port = event.port[0]; port.onmessage(event) { port.postMessage(event.data[1] - event.data[0]); } }
SharedWorker
的使用中,我們發現對於
SharedWorker
例項物件,我們需要通過
port
屬性來訪問到主要方法;同時在
Worker
指令碼中,多了個全域性的
connect()
函式,同時在函式中也需要去獲取一個
port
物件來進行啟動以及操作;這是因為,多頁頁面共享一個
SharedWorker
執行緒時,線上程中需要去判斷和區,分來自不同頁面的資訊,這是最主要的區別和原因。
在 Worker
執行緒中, self
和 this
都代表子執行緒的全域性物件。對於監聽 message
事件,以下的四種寫法是等同的。
// 寫法 1 self.addEventListener('message', function (e) { // ... }) // 寫法 2 this.addEventListener('message', function (e) { // ... }) // 寫法 3 addEventListener('message', function (e) { // ... }) // 寫法 4 onmessage = function (e) { // ... }
- 示例
關閉執行緒
myWorker.terminate(); // 主執行緒中使用
close(); worker執行緒中使用(推薦)
錯誤處理
// 主執行緒 myWorker.onerror = function(event) { const lineno = event.lineno;// 出錯的指令碼名稱 const filename = event.filename;// 發生錯誤的行號 const message = event.message;// 對錯誤的描述 }
// 不能進行反序列化時觸發 myWorker.onmessageerror = function() { ... }// 專用執行緒 myWorker.port.onmessageerror function() {...} // 共享執行緒
外部載入指令碼
提供 importScript()
方法,匯入一條或者以上指令碼到當前 worker
的作用域裡.
每個指令碼中的全域性物件都能夠被 worker 使用.
importScript('first.js', 'second.js');
子程序
Worker
可以生成子程序
- 存在同源限制
- 子
Worker
中的URL
相對於父級Woker
所在位置進行解析
嵌入Worker
<script type="text/js-worker"> onmessage = (event) => { postMessage(event.data + 1); } </script> <script> const workerScript = document.querySelector('script[type="text/js-worker"]'); const blob = new Bolb(workerScript, { type: 'text/javascript' }); const myWorker = new Worker(window.URL.createObjectURL(blob)); myWorker.postMessage(1); myWorker.onmessage = (event) => { console.log('來自子執行緒訊息:', event.data); } </script>
結構化克隆演算法
結構化克隆演算法是由 HTML5
規範定義的用於複製複雜 JavaScript
物件的演算法。通過來自 Workers
的 postMessage()
或使用 IndexedDB
儲存物件時在內部使用。它通過遞迴輸入物件來構建克隆,同時保持先前訪問過的引用的對映,以避免無限遍歷迴圈。這一過程可以理解為,在傳送方使用類似 JSON.stringfy()
的方法將引數序列化,在接收方採用類 JSON.parse()
的方法反序列化。
-
Error
以及Function
物件是不能被結構化克隆演算法複製的;如果你嘗試這樣子去做,這會導致丟擲DATA_CLONE_ERR
的異常 - 無法克隆
DOM
-
物件的某些特定引數也不會被保留
-
RegExp
物件的lastIndex
欄位不會被保留 - 屬性描述符,setters 以及 getters(以及其他類似元資料的功能)同樣不會被複制。例如,如果一個物件用屬性描述符標記為 read-only,它將會被複製為 read-write,因為這是預設的情況下
- 原形鏈上的屬性也不會被追蹤以及複製
-
Web Worker
中可以使用的函式和類
- 時間相關
clearInterval() clearTimeout() setInterval() setTimeout
-
Worker
相關
importScripts() close() postMessage()
- 儲存相關
Cache IndexedDB
- 網路相關
Fetch WebSocket XMLHttpRequest