Web Worker 概述
使用場(chǎng)景
js采用的是單線程模型阶冈,所有任務(wù)只能在一個(gè)線程上完成缤苫,但是計(jì)算機(jī)目前大部分都是多核cpu的诵叁,通過(guò)Web Worker為js創(chuàng)造多線程環(huán)境业栅。
主線程創(chuàng)建Worker線程冈欢,在主線程運(yùn)行的同時(shí)歉铝,Worder線程在后臺(tái)運(yùn)行,不會(huì)影響主線程涛癌,當(dāng)計(jì)算出結(jié)果后再將其返回給主線程犯戏。
可以將一些計(jì)算密集型或者高延遲的任務(wù)交給Worker線程,主線程主要來(lái)負(fù)責(zé)UI交互拳话,使頁(yè)面更流暢先匪。
注意
Worker線程新建成功后,會(huì)始終運(yùn)行弃衍,需要主動(dòng)關(guān)閉呀非。
主要是利于隨時(shí)響應(yīng)主進(jìn)程的通信。
- 同源限制
Worker線程運(yùn)行的文件必須和主線程的文件同源镜盯。
- DOM限制
Worker線程的全局對(duì)象與主線程不一樣岸裙,不能獲取
DOM對(duì)象
、document
速缆、window
降允、parent
。
可以獲取
navigator
和location
艺糜。
- 通信聯(lián)系
不能直接通信剧董,必須通過(guò)消息完成幢尚。
- 腳本限制
Worker線程不能執(zhí)行
alert()
和confirm()
,但是可以執(zhí)行console
翅楼,也可以使用XMLHttpRequest
對(duì)象發(fā)出ajax尉剩。
- 文件限制
Worker線程不能讀取本地文件,必須來(lái)自網(wǎng)絡(luò)毅臊,本地的可以通過(guò)
Blob
和URL.createObjectURL()
實(shí)現(xiàn)理茎。
Web Worder使用
初步使用
主線程
- 創(chuàng)建Worker線程和通信
/**
* 創(chuàng)建一個(gè)Worker線程
* worker.js必須來(lái)自網(wǎng)絡(luò),如果下載失敗不會(huì)有任何提示
*/
const worker = new Worker('worker.js');
/**
* 主線程向Worder線程發(fā)送數(shù)據(jù)
* 也可以傳二進(jìn)制數(shù)據(jù)
*/
worker.postMessage('test1');
worker.postMessage({
method: 'send',
arg: ['test2']
});
/**
* 主線程接收Worler線程發(fā)送數(shù)據(jù)
* 指定一個(gè)回調(diào)函數(shù)監(jiān)聽(tīng)
*/
worker.onmessage = (event) => {
// event.data Worker線程發(fā)送到主線程的數(shù)據(jù)
const data = event.data;
// 告訴Worker線程可以關(guān)閉的信息管嬉,在Worker中處理關(guān)閉
worker.postMessage('done');
// 也可以主線程關(guān)閉
worker.terminate();
}
- 主線程可以監(jiān)聽(tīng)Worker是否出錯(cuò)皂林,如果出錯(cuò)會(huì)觸發(fā)主線程的
error
事件
worker.addEventListener('error', (e) => {
console.log([
'ERROR: Line ', e.lineno, ' in ', e.filename, ': ', e.message
].join(''));
});
// 或
worker.onerror((e) => {
console.log([
'ERROR: Line ', e.lineno, ' in ', e.filename, ': ', e.message
].join(''));
});
- 關(guān)閉Worker
// 主線程
worker.terminate();
// Worker線程
self.close();
Worker線程
Worker線程內(nèi)部
self
和this
代表子線程的自身Worker線程內(nèi)部需要有一個(gè)監(jiān)聽(tīng)函數(shù),監(jiān)聽(tīng)
message
事件蚯撩。
三種寫法
// 1
self.addEventListener('message', (e) => {
// 接收到的數(shù)據(jù)
const data = e.data;
}, false);
// 2
this.addEventListener('message', (e) => {
// 接收到的數(shù)據(jù)
const data = e.data;
}, false);
// 3
addEventListener('message', (e) => {
// 接收到的數(shù)據(jù)
const data = e.data;
}, false);
監(jiān)聽(tīng)除了監(jiān)聽(tīng)message
事件式撼,也可以指定函數(shù)
- 向主線程發(fā)送數(shù)據(jù)用
self.postMessage()
self.onmessage = (e) => {
const data = e.data;
// 向主線程發(fā)送數(shù)據(jù)
self.postMessage('Worker send');
}
- 關(guān)閉Worker線程(關(guān)閉自身)
self.close();
- Worker加載腳本
Worker內(nèi)部如果要加載其他的js,要用importScripts()
importScripts('a.js');
// 也可以同時(shí)加載多個(gè)js
importScripts('a.js', 'b.js');
數(shù)據(jù)通信
- 主線程和Worker線程之間的通信求厕,除了二進(jìn)制
File、Blob扰楼、ArrayBuffer
等數(shù)據(jù)類型外呀癣,都是類似于JSON.parse(JSON.strify())
這種傳值,并不會(huì)影響到本線程以外的數(shù)據(jù)改動(dòng)弦赖。
事實(shí)上项栏,瀏覽器內(nèi)部的運(yùn)行機(jī)制是,先將通信內(nèi)容串行化蹬竖,然后把串行化后的字符串發(fā)給 Worker沼沈,后者再將它還原。
- 主線程和Worker線程之間交換二進(jìn)制數(shù)據(jù)币厕,上面拷貝的方式會(huì)造成性能問(wèn)題列另,因?yàn)橐话銛?shù)據(jù)量大的會(huì)用二進(jìn)制流方式去傳輸。為了避免這個(gè)問(wèn)題旦装,js允許主線程把二進(jìn)制數(shù)據(jù)直接轉(zhuǎn)移給worker線程页衙,但是在轉(zhuǎn)移沒(méi)有完成之前,主線程無(wú)法使用二進(jìn)制數(shù)據(jù)阴绢,這是為了防止出現(xiàn)多個(gè)線程同時(shí)修改數(shù)據(jù)的麻煩局面店乐。這種轉(zhuǎn)移數(shù)據(jù)的方法,叫做Transferable Objects呻袭。
Transferable Objects 格式:
worker.postMessage(arrayBuffer, [arrayBuffer]);
例如:
const ab = new ArrayBuffer(1);
worker.postMessage(ab, [ab]);
例子
- Worker 線程完成輪詢
有時(shí)眨八,瀏覽器需要輪詢服務(wù)器狀態(tài),以便第一時(shí)間得知狀態(tài)改變左电。這個(gè)工作可以放在 Worker 里面廉侧。
function createWorker(f) {
var blob = new Blob(['(' + f.toString() +')()']);
var url = window.URL.createObjectURL(blob);
var worker = new Worker(url);
return worker;
}
var pollingWorker = createWorker(function (e) {
var cache;
function compare(new, old) { ... };
setInterval(function () {
fetch('/my-api-endpoint').then(function (res) {
var data = res.json();
if (!compare(data, cache)) {
cache = data;
self.postMessage(data);
}
})
}, 1000)
});
pollingWorker.onmessage = function () {
// render data
}
pollingWorker.postMessage('init');
常用API
主線程
- 瀏覽器原生提供Worker()構(gòu)造函數(shù)页响,用來(lái)供主線程生成 Worker 線程。
var myWorker = new Worker(jsUrl, options);
Worker()構(gòu)造函數(shù)伏穆,可以接受兩個(gè)參數(shù)拘泞。第一個(gè)參數(shù)是腳本的網(wǎng)址(必須遵守同源政策),該參數(shù)是必需的枕扫,且只能加載 JS 腳本陪腌,否則會(huì)報(bào)錯(cuò)。第二個(gè)參數(shù)是配置對(duì)象烟瞧,該對(duì)象可選诗鸭。它的一個(gè)作用就是指定 Worker 的名稱,用來(lái)區(qū)分多個(gè) Worker 線程参滴。
// 主線程
var myWorker = new Worker('worker.js', { name : 'myWorker' });
// Worker 線程
self.name // myWorker
- Worker()構(gòu)造函數(shù)返回一個(gè) Worker 線程對(duì)象强岸,用來(lái)供主線程操作 Worker。Worker 線程對(duì)象的屬性和方法如下:
- Worker.onerror:指定 error 事件的監(jiān)聽(tīng)函數(shù)砾赔。
- Worker.onmessage:指定 message 事件的監(jiān)聽(tīng)函數(shù)蝌箍,發(fā)送過(guò)來(lái)的數(shù)據(jù)在Event.data屬性中。
- Worker.onmessageerror:指定 messageerror 事件的監(jiān)聽(tīng)函數(shù)暴心。發(fā)送的數(shù)據(jù)無(wú)法序列化成字符串時(shí)妓盲,會(huì)觸發(fā)這個(gè)事件。
- Worker.postMessage():向 Worker 線程發(fā)送消息专普。
- Worker.terminate():立即終止 Worker 線程悯衬。
Worker線程
- self.name: Worker 的名字。該屬性只讀檀夹,由構(gòu)造函數(shù)指定筋粗。
- self.onmessage:指定message事件的監(jiān)聽(tīng)函數(shù)。
- self.onmessageerror:指定 messageerror 事件的監(jiān)聽(tīng)函數(shù)炸渡。發(fā)送的數(shù)據(jù)無(wú)法序列化成字符串時(shí)娜亿,會(huì)觸發(fā)這個(gè)事件。
- self.close():關(guān)閉 Worker 線程偶摔。
- self.postMessage():向產(chǎn)生這個(gè) Worker 線程發(fā)送消息暇唾。
- self.importScripts():加載 JS 腳本。
webpack應(yīng)用
worker-loader
安裝
yarn add -D worker-loader
webpack配置
- webpack.config.js
{
module: {
rules: [
{
test: /\.worker\.js$/,
use: { loader: 'worker-loader' }
}
]
}
}
- vue.config.js(vue-cli3)
module.exports = {
chainWebpack: (config) => {
// 新增加一個(gè)loader
config.module.rule('worker').test(/\.worker\.js$/).use('worker-loader').loader('worker-loader').options({ name: '[name].[hash].js' }).end()
},
// 如果開啟多進(jìn)程構(gòu)建辰斋,worker-loader 會(huì)報(bào) Cannot read property 'createChildCompiler' of undefined
parallel: false
}
項(xiàng)目中使用
- file.worker.js
import marked from 'marked';
self.addEventListener('message', (e) => {
const data = e.data
const md = marked(data, { sanitize: true });
self.postMessage(md)
})
- App.js
import markdownWorder from './file.worker.js';
const worker = new markdownWorder()
const markdownText = `# demo
## demo1`
worker.postMessage(markdownText)
worker.onmessage = (event) => {
const HTML = event.data
}