提到 HTML5 總是讓人津津樂道,太多的特性和有趣的 API 讓人耳目一新。但是很多童鞋還停留在語義化的階段,忽視了 HTML5 的強(qiáng)勁之處。
這節(jié)我們來探討一下多線程 Web-Worker。
一、明確 JavaScript 是單線程
JavaScript 語言的一大特點(diǎn)就是單線程,也就是說,同一個時間只能做一件事。
聽起來有些匪夷所思,為什么不設(shè)計(jì)成多線程提高效率呢?我們可以假設(shè)一種場景:
假定 JavaScript
同時有兩個線程,一個線程在某個 DOM
節(jié)點(diǎn)上添加內(nèi)容,另一個線程刪除了這個節(jié)點(diǎn),這時瀏覽器應(yīng)該以哪個線程為準(zhǔn)?
作為瀏覽器腳本語言, JavaScript
的主要用途是與用戶互動,以及操作 DOM
。
這決定了它只能是單線程,否則會帶來很復(fù)雜的同步問題。為了避免復(fù)雜性,從一誕生, JavaScript
就是單線程,這已經(jīng)成了這門語言的核心特征,估計(jì)短期內(nèi)很難改變。
二、新曙光:Web Worker
單線程始終是一個痛點(diǎn),為了利用多核 CPU
的計(jì)算能力, HTML5
提出 Web Worker
標(biāo)準(zhǔn),允許 JavaScript
腳本創(chuàng)建多個線程。但是子線程完全受主線程控制,且不得操作 DOM
。
所以,這個新標(biāo)準(zhǔn)并沒有改變 JavaScript
單線程的本質(zhì)。
Web Workers
是現(xiàn)代瀏覽器提供的一個 JavaScript
多線程解決方案,我們可以找到很多使用場景:
1.我們可以用 Web Worker
做一些大計(jì)算量的操作;
2.可以實(shí)現(xiàn)輪詢,改變某些狀態(tài);
3.頁頭消息狀態(tài)更新,比如頁頭的消息個數(shù)通知;
4.高頻用戶交互,拼寫檢查,譬如:根據(jù)用戶的輸入習(xí)慣、歷史記錄以及緩存等信息來協(xié)助用戶完成輸入的糾錯、校正功能等
5.加密:加密有時候會非常地耗時,特別是如果當(dāng)你需要經(jīng)常加密很多數(shù)據(jù)的時候(比如,發(fā)往服務(wù)器前加密數(shù)據(jù))。
6.預(yù)取數(shù)據(jù):為了優(yōu)化網(wǎng)站或者網(wǎng)絡(luò)應(yīng)用及提升數(shù)據(jù)加載時間,你可以使用 Workers
來提前加載部分?jǐn)?shù)據(jù)以備不時之需。
加密是一個使用 Web Worker
的絕佳場景,因?yàn)樗⒉恍枰L問 DOM
或者利用其它魔法,它只是純粹使用算法進(jìn)行計(jì)算而已。隨著大眾對個人敏感數(shù)據(jù)的日益重視,信息安全和加密也成為重中之重。這可以從近期的 12306 用戶數(shù)據(jù)泄露事件中體現(xiàn)出來。
一旦在 Worker 進(jìn)行計(jì)算,它對于用戶來說是無縫地且不會影響到用戶體驗(yàn)。
三、兼容性
四、基本概念
1.首先記得去判斷是否支持
if (window.Worker) {
...
}
2.創(chuàng)建一個新的 worker
很簡單
const myWorker = new Worker('worker.js');
postMessage() 方法和 onmessage 事件處理函數(shù)是 Workers 的黑魔法。
3. postMessage
用來發(fā)送消息,而 onmessage
用來監(jiān)聽消息
const worker = new Worker('src/worker.js');
worker.onmessage = e => {
console.log(e.data);
};
worker.postMessage('你好嗎!');
在主線程中使用時, onmessage
和 postMessage()
必須掛在 worker
對象上,而在 worker
中使用時不用這樣做。原因是,在 worker
內(nèi)部, worker
是有效的全局作用域。
4.異常處理:
worker.onerror = function(error) {
console.log(error.message);
throw error;
};
5.終止 worker
worker
線程會被立即殺死,不會有任何機(jī)會讓它完成自己的操作或清理工作。
6.在 worker
線程中, workers
也可以調(diào)用自己的 close
方法進(jìn)行關(guān)閉:
五、快速開始
為了快速掌握,我們來做一個小例子:項(xiàng)目結(jié)構(gòu)如下
├── index.html
└── src
├── main.js
└── worker.js
Html
<html>
<head>
<title>Web Work Demo</title>
<meta charset="UTF-8" />
</head>
<body>
<div id="app"> Hello Jartto! </div>
<script src="src/main.js"></script>
</body>
</html>
main.js
const worker = new Worker('src/worker.js');
worker.onmessage = e => {
const message = e.data;
console.log(`[From Worker]: ${message}`);
document.getElementById('app').innerHTML = message;
};
worker.postMessage('寫的真好!');
Work.js
onmessage = e => {
const message = e.data;
console.log(`[From Main]: ${message}`);
if(message.indexOf('好') > -1) {
postMessage('謝謝支持');
}
};
代碼很簡單,主線程發(fā)送:「寫的真好!」
web worker 收到消息,發(fā)現(xiàn)內(nèi)容中含有「好」字,回傳給主線程:「謝謝支持」
六、局限性
1.在 worker
內(nèi),不能直接操作 DOM
節(jié)點(diǎn),也不能使用 window
對象的默認(rèn)方法和屬性。然而我們可以使用大量 window
對象之下的東西,包括 WebSockets
, IndexedDB
以及 FireFox OS
專用的 Data Store API
等數(shù)據(jù)存儲機(jī)制。
這里舉個例子,我們修改 main.js
:
const worker = new Worker('src/worker.js');
worker.onmessage = e => {
const message = e.data;
console.log(`[From Worker]: ${message}`);
document.getElementById('app').innerHTML = message;
};
+ worker.onerror = function(error) {
+ console.log(error);
+ worker.terminate();
+ };
worker.postMessage('寫的真好!');
再來修改 work.js
+ alert('jartto');
onmessage = e => {
const message = e.data;
console.log(`[From Main]: ${message}`);
if(message.indexOf('好') > -1) {
postMessage('謝謝支持');
}
};
這時候運(yùn)行就會報(bào)出:
這是因?yàn)椋?worker.js
執(zhí)行的上下文,與主頁面 HTML
執(zhí)行時的上下文并不相同,最頂層的對象并不是 Window
, woker.js
執(zhí)行的全局上下文,而是 WorkerGlobalScope
,我們具體說明。
2. workers
和主線程間的數(shù)據(jù)傳遞通過這樣的消息機(jī)制進(jìn)行:雙方都使用 postMessage()
方法發(fā)送各自的消息,使用 onmessage
事件處理函數(shù)來響應(yīng)消息(消息被包含在 Message
事件的 data
屬性中)。
這個過程中數(shù)據(jù)并不是被共享而是被復(fù)制。
3.同源限制
分配給 Worker
線程運(yùn)行的腳本文件,必須與主線程的腳本文件同源。
4.文件限制
Worker
線程無法讀取本地文件,即不能打開本機(jī)的文件系統(tǒng) (file://)
,它所加載的腳本,必須來自服務(wù)器。
5.不允許本地文件
Uncaught SecurityError: Failed to create a worker:
script at '(path)/worker.js'
cannot be accessed from origin 'null'.
Chrome doesn’t let you load web workers when running scripts from a local file.
那如何解決呢?我們可以啟動一個本地服務(wù)器,建議使用 http-server
,簡單易用。
6.內(nèi)容安全策略
有別于創(chuàng)建它的 document
對象, worker
有它自己的執(zhí)行上下文。因此普遍來說, worker
并不受限于創(chuàng)建它的 document
(或者父級 worker
)的內(nèi)容安全策略。
我們來舉個例子,假設(shè)一個 document
有如下頭部聲明:
Content-Security-Policy: script-src 'self'
這個聲明有一部分作用在于,禁止它內(nèi)部包含的腳本代碼使用 eval()
方法。然而,如果腳本代碼創(chuàng)建了一個 worker
,在 worker
上下文中執(zhí)行的代碼卻是可以使用 eval()
的。
為了給 worker 指定 CSP,必須為發(fā)送 worker 代碼的請求本身加上一個 CSP。
有一個例外情況,即 worker
腳本的源如果是一個全局性的唯一的標(biāo)識符(例如,它的 URL
指定了數(shù)據(jù)模式或者 blob
), worker
則會繼承創(chuàng)建它的 document
或者 worker
的 CSP
。
七、擴(kuò)展:WorkerGlobalScope
關(guān)于 ,我們可以在 MDN
上面找到文檔:
1. self
:
我們可以使用 WorkerGlobalScope
的 self
屬性來獲取這個對象本身的引用。
2. location
:
location
屬性返回當(dāng)線程被創(chuàng)建出來的時候與之關(guān)聯(lián)的 WorkerLocation
對象,它表示用于初始化這個工作線程的腳步資源的絕對 URL
,即使頁面被多次重定向后,這個 URL
資源位置也不會改變。
3. close
:
關(guān)閉當(dāng)前線程,與 terminate
作用類似。
4. caches
:
當(dāng)前上下文得 CacheStorage
,確保離線可用,同時可以自定義請求的響應(yīng)。
5. console
:
支持 console
語法。
6. importScripts
我們可以通過 importScripts()
方法通過 url
在 worker
中加載庫函數(shù)。
7. XMLHttpRequest
有了它,才能發(fā)出 Ajax
請求。
8.可以使用:
- setTimeout/setInterval
- addEventListener/postMessage
還有很多 API
可以使用,這里就不一一舉例了。
八、異常處理
當(dāng) worker
出現(xiàn)運(yùn)行中錯誤時,它的 onerror
事件處理函數(shù)會被調(diào)用。它會收到一個擴(kuò)展了 ErrorEvent
接口的名為 error
的事件。該事件不會冒泡并且可以被取消。
為了防止觸發(fā)默認(rèn)動作,worker 可以調(diào)用錯誤事件的 preventDefault() 方法。
錯誤事件我們常用如下這三個關(guān)鍵信息:
- Message:可讀性良好的錯誤消息;
- Filename:發(fā)生錯誤的腳本文件名;
- Lineno:發(fā)生錯誤時所在腳本文件的行號;
worker.onerror = function(error) {
console.log(error.message);
throw error;
};
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。