基礎使用javascript
動態內聯workerhtml
subworkerjava
你們好,今天在這裏簡單介紹一下web workers的基本使用。webpack
web workers可使得一些涉及複雜計算的邏輯在獨立的線程運行,從而不會影響頁面的性能,例如渲染、交互響應等。git
web workers中可使用大多數標準的JS特性,例如XMLHttpRequest等;固然web workers也有很多侷限行,例如不能操做DOM、做用域是獨立的等等。github
下面來一塊兒看一個簡單的web workers例子。web
咱們首先建立一個worker.js
的空文件。咱們將在其中編寫worker的代碼。chrome
而後咱們經過new Worker(workerFilePath)
的方式創建一個worker。瀏覽器
// index.html
const worker = new Worker('./worker.js');
複製代碼
主線程和worker經過postMessage
和onmessage
來通訊。安全
首先,咱們在主線程中經過worker.postMessage
向worker發送消息。
// index.html
worker.postMessage('start');
複製代碼
接着,咱們在worker.js
中定義onmessage
來處理接收到的消息:
// worker.js
onmessage = function (ev) {
if (ev.data === 'start') {
timedCount();
}
};
複製代碼
onmessage
會收到一個MessageEvent
事件做爲參數,其中的data
屬性就是咱們在postMessage
中發送的數據。
一樣的,咱們能夠在worker.js
中使用postMessage
向主線程發送消息:
// worker.js
const timedCount = function timedCount() {
i += 1;
postMessage(i);
setTimeout(timedCount, 500);
};
複製代碼
在主線程中咱們經過worker.onmessage
接受worker
發來的消息,並能夠調用worker.terminate
來終止worker
:
// index.html
worker.onmessage = function onmessage(ev) {
document.getElementById('result').innerHTML = ev.data;
if (ev.data === 10) worker.terminate();
};
複製代碼
固然,咱們可使用worker.js
建立多個worker:
// index.html
const worker1 = new Worker('./worker.js');
const worker2 = new Worker('./worker.js');
const anotherWorker = new Worker('./anotherWorker.js');
複製代碼
咱們可使用importScript
在web workers中引入其它代碼,importScripts
是同步加載代碼的,加載的代碼中暴露的全局對象可以被worker使用,例如:
// worker.js
importScripts('./foo.js', './bar.js');
foo();
bar();
// foo.js
function foo() {
console.log('foo');
};
// bar.js
function bar() {
console.log('bar.js');
};
複製代碼
注意這裏引入代碼的路徑是與當前worker代碼所在的路徑對應的。
因爲在主線程和web workers之間傳遞數據須要通過拷貝過程,所以當咱們在postMessage
中發送大型數據時性能會收到影響。而可轉讓對象使得數據在上下文切換的過程當中沒必要通過任何拷貝操做,從而大大提升了性能。
當數據被轉移後,在原上下文中將被清除而不復存在:
// index.html
const worker = new Worker('./js/arrayBuffer.js');
const uInt8Array = new Uint8Array(1024);
for (var i = 0; i < uInt8Array .length; ++i) {
uInt8Array[i] = i;
}
worker.postMessage(uInt8Array.buffer, [uInt8Array.buffer]);
console.log(uInt8Array.byteLength); // 0
// arrayBuffer.js
onmessage = function (ev) {
const uInt8View = new Uint8Array(ev.data);
console.log(uInt8View.byteLength, uInt8View[11]);
};
複製代碼
共享內存是另外一個使得主線程與web workers以前能夠高效傳遞(共享)數據的方式。SharedArrayBuffer即是其實現。不過因爲安全問題這一特性最終被關閉。
咱們能夠經過worker-loader來在webpack中使用web worker。
import Worker from 'worker-loader!./worker.js';
const worker = new Worker();
worker.postMessage('start');
worker.onmessage = function(ev) {};
複製代碼
隨後咱們打包代碼,在打包後的代碼中咱們應該能夠看到相似下面的代碼:
module.exports = function() {
return new Worker(__webpack_require__.p + "61a1c1b143c4403de10b.worker.js");
};
複製代碼
在這裏能夠看到worker-loader
使用new Worker(url)
的方式來加載web worker。
若是咱們將以上的代碼修改成:
import Worker from 'worker-loader?inline=true&fallback=false!./worker.js';
複製代碼
並從新打包。此次咱們將在vendor.js
(根據你的配置也多是其餘文件名)中找到相似以下的代碼:
module.exports = function (content, url) {
try {
try {
var blob;
try {
// BlobBuilder = Deprecated, but widely implemented
var BlobBuilder = window.BlobBuilder || window.WebKitBlobBuilder || window.MozBlobBuilder || window.MSBlobBuilder;
blob = new BlobBuilder();
blob.append(content);
blob = blob.getBlob();
} catch (e) {
// The proposed API
blob = new Blob([content]);
}
return new Worker(URL.createObjectURL(blob));
} catch (e) {
return new Worker('data:application/javascript,' + encodeURIComponent(content));
}
} catch (e) {
if (!url) {
throw Error('Inline worker is not supported');
}
return new Worker(url);
}
};
複製代碼
這裏worker-loader
會嘗試經過new Blob
以及URL.createObjectURL
來將worker的代碼內聯,這裏的content
就是相應worker的代碼文本。固然若是失敗仍是會回退爲使用url
加載web worker。
Shared worket提供了一種跨窗口/iframe等通訊的途徑。接下來咱們用chrome來運行一下demo。
// index.html
<script type="text/javascript">
const id = (+new Date()).toString(32);
const myWorker = new SharedWorker('./js/shared.js');
myWorker.port.start();
myWorker.port.onmessage = function(ev) {
const { type, id, data } = ev.data;
switch(type) {
case 'created':
console.log(data);
break;
}
};
myWorker.port.postMessage({
id,
type: 'start',
});
</script>
複製代碼
在頁面上咱們使用(+new Date()).toString(32)
來將當前時間戳轉換而成的字符串做爲id(這裏咱們假設它的惟一性是有保障的)。
而後咱們經過new SharedWorker(filePath)
來加載shared worker,而且用myWorker.port.start()
啓動它。這會觸發shared worker的onconnect
。
接着咱們經過port.onmessage
設置消息接收回調,並使用port.postMessage
向其發出一條消息。
const ids = [];
const ports = [];
const broadcast = function (data) {
for (let port of ports) {
port.postMessage(data);
}
};
onconnect = function(e) {
const port = e.ports[0];
ports.push(port);
port.addEventListener('message', (ev) => {
const { type, id } = ev.data;
switch(type) {
case 'start':
ids.push(id);
broadcast({
id,
type: 'created',
data: ids,
});
break;
}
});
port.start();
};
複製代碼
每當有shared worker接入,咱們都在onconnect
中將對應的port
保存下來,這樣咱們就能實現廣播機制。
咱們經過port.addEventListener('message', handler)
的方式添加接受消息的偵聽,以後用port.start()
啓動它。
當咱們收到start
消息後,咱們將新接入的信道的id
保存下來,並這一消息廣播出來(發送給全部已連接的ports
)。
若是咱們前後在chrome
瀏覽器中打開多個tab
並打開咱們的頁面,就能看到新信道接入的消息被廣播了。經過這樣的方式,咱們就能在多個窗口/iframe等之間實現通訊了。
今天介紹了web workers的基本使用,接下來要介紹一下如何動態的建立內聯的web workers。