一磅叛、概述
1秽荤、介紹
“JavaScript是單線程的”這一說法它事實(shí)上描述了JavaScript環(huán)境在瀏覽器內(nèi)的一般行為纷责。
這樣的單線程范例本身是有局限的哼丈,因?yàn)樗恋K了在語言中其他可行的編程模式边器,否則可將工作委托給獨(dú)立的線程或進(jìn)程训枢。JavaScript被限定到這一單線程范例上,是為了讓它和各種瀏覽器API交互時(shí)保持兼容性忘巧。例如DOM如果被多個(gè)JavaScript線程同時(shí)修改會(huì)出現(xiàn)問題恒界。因此,不能使用傳統(tǒng)的并發(fā)結(jié)構(gòu)增強(qiáng)JavaScript砚嘴。
Web Worker 的作用十酣,就是為 JavaScript 創(chuàng)造多線程環(huán)境,允許主線程創(chuàng)建 Worker 線程际长,將一些任務(wù)分配給后者運(yùn)行耸采。在主線程運(yùn)行的同時(shí),Worker 線程在后臺(tái)運(yùn)行工育,兩者互不干擾虾宇。等到 Worker 線程完成計(jì)算任務(wù),再把結(jié)果返回給主線程如绸。這樣的好處是嘱朽,一些計(jì)算密集型或高延遲的任務(wù)旭贬,被 Worker 線程負(fù)擔(dān)了,主線程(通常負(fù)責(zé) UI 交互)就會(huì)很流暢搪泳,不會(huì)被阻塞或拖慢稀轨。
workers的核心觀點(diǎn)是:允許主執(zhí)行線程委托工作給獨(dú)立的實(shí)體,而不用改變現(xiàn)存的單線程模型岸军。
2奋刽、Web Worker與線程的比較
相同特性:
- workers是用實(shí)際的線程實(shí)現(xiàn)的。例如Blink瀏覽器引擎使用WorkerTread實(shí)現(xiàn)workers凛膏,它對(duì)應(yīng)一個(gè)底層的平臺(tái)線程杨名。
- workers并行執(zhí)行。盡管頁面和worker的實(shí)現(xiàn)都是單線程JavaScript環(huán)境猖毫,各個(gè)環(huán)境的指令都是并行執(zhí)行的。
- workers可以共享一些內(nèi)存须喂。workers可以使用SharedArrayBuffer 在多環(huán)境中共享內(nèi)存吁断。線程使用鎖來實(shí)現(xiàn)并行控制,而JavaScript使用Atomics接口實(shí)現(xiàn)并行控制坞生。
不同點(diǎn):
- workers不會(huì)共享所有內(nèi)存仔役。在傳統(tǒng)的線程模型中,多線程有能力讀寫共享內(nèi)存空間是己。除了sharedArrayBuffer之外又兵,在workers移動(dòng)數(shù)據(jù)的進(jìn)出需要對(duì)數(shù)據(jù)復(fù)制或者轉(zhuǎn)存。
- worker線程不必是同一進(jìn)程的一部分卒废。通常沛厨,單個(gè)進(jìn)程可以在它內(nèi)部生成多個(gè)線程。取決于瀏覽器引擎的實(shí)現(xiàn)方法摔认,worker線程可以是逆皮,也可以不是頁面進(jìn)程的一部分。例如Chrome的Blink引擎使用獨(dú)立進(jìn)程實(shí)現(xiàn)共享worker和服務(wù)worker線程参袱。
- worker線程的創(chuàng)建成本更昂貴电谣。worker線程包括自己的獨(dú)立事件循環(huán),全局對(duì)象抹蚀,事件處理程序剿牺,和其他JavaScript環(huán)境中的部分特性。創(chuàng)建這些線程的計(jì)算開銷不可忽視环壤。
注意:在形式和功能上晒来,workers都不是線程的簡(jiǎn)單替代。
workers相對(duì)更重镐捧,不建議大量使用潜索。例如臭增,對(duì)四百萬像素的圖片的每個(gè)像素都啟動(dòng)一個(gè)worker是不合適的。一般來說竹习,我們預(yù)期workers是持久性的誊抛,有較高的啟動(dòng)性能花銷,和較高的每實(shí)例內(nèi)存開銷整陌。所以拗窃,web worker有利于隨時(shí)響應(yīng)主線程的通信。但是泌辫,這也造成了 Worker 比較耗費(fèi)資源随夸,不應(yīng)該過度使用,而且一旦使用完畢震放,就應(yīng)該關(guān)閉宾毒。
二、Web Worker
1殿遂、Workers類型
(1)專用Worker
專用web worker诈铛,一般也稱為專用worker,web worker墨礁,或就叫worker幢竹,是允許腳本生成獨(dú)立JavaScript線程并委托任務(wù)的基本應(yīng)用。專用worker正如它的名字所表明的恩静,只能由創(chuàng)建它的頁面訪問焕毫。
(2)共享Worker
共享web worker的行為很像專用worker。主要的區(qū)別是共享worker可以讓多個(gè)環(huán)境訪問驶乾,包括不同的頁面邑飒。任意腳本執(zhí)行所在的源如果與最初生成共享worker的腳本所在的源相同,就可以發(fā)送和接受共享worker的消息轻掩。
(3)其他
- ServiceWorkers (服務(wù)worker)一般作為web應(yīng)用程序幸乒、瀏覽器和網(wǎng)絡(luò)(如果可用)之前的代理服務(wù)器。它們旨在(除開其他方面)創(chuàng)建有效的離線體驗(yàn)唇牧,攔截網(wǎng)絡(luò)請(qǐng)求罕扎,以及根據(jù)網(wǎng)絡(luò)是否可用采取合適的行動(dòng)并更新駐留在服務(wù)器上的資源。他們還將允許訪問推送通知和后臺(tái)同步API丐重。
- Chrome Workers 是一種僅適用于firefox的worker腔召。如果您正在開發(fā)附加組件,希望在擴(kuò)展程序中使用worker且有在你的worker中訪問 js-ctypes 的權(quán)限扮惦,你可以使用Chrome Workers臀蛛。詳情請(qǐng)參閱
ChromeWorker
。 - Audio Workers (音頻worker)使得在web worker上下文中直接完成腳本化音頻處理成為可能。
2浊仆、使用web worker的注意事項(xiàng)
(1)同源限制
分配給 Worker 線程運(yùn)行的腳本文件客峭,必須與主線程的腳本文件同源。
(2)DOM 限制
Worker 線程所在的全局對(duì)象抡柿,與主線程不一樣舔琅,無法讀取主線程所在網(wǎng)頁的 DOM 對(duì)象,也無法使用document洲劣、window备蚓、parent這些對(duì)象。但是囱稽,Worker 線程可以navigator對(duì)象和location對(duì)象郊尝。
(3)通信聯(lián)系
Worker 線程和主線程不在同一個(gè)上下文環(huán)境,它們不能直接通信战惊,必須通過消息完成流昏。
(4)腳本限制
Worker 線程不能執(zhí)行alert()方法和confirm()方法,但可以使用 XMLHttpRequest 對(duì)象發(fā)出 AJAX 請(qǐng)求吞获。
(5)文件限制
Worker 線程無法讀取本地文件横缔,即不能打開本機(jī)的文件系統(tǒng)(file://),它所加載的腳本衫哥,必須來自網(wǎng)絡(luò)。
3襟锐、基本用法
(1)專用Worker
代碼示例:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport" content="width=device-width">
<title>Web Workers basic example</title>
<link rel="stylesheet" href="style.css">
<!--[if lt IE 9]>
<script src="http://html5shiv.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
</head>
<body>
<h1>Web<br>Workers<br>basic<br>example</h1>
<div class="controls" tabindex="0">
<form>
<div>
<label for="number1">Multiply number 1: </label>
<input type="text" id="number1" value="0">
</div>
<div>
<label for="number2">Multiply number 2: </label>
<input type="text" id="number2" value="0">
</div>
</form>
<p class="result">Result: 0</p>
</div>
<script src="main.js"></script>
</body>
</html>
const first = document.querySelector('#number1');
const second = document.querySelector('#number2');
const result = document.querySelector('.result');
if (window.Worker) {
const myWorker = new Worker("worker.js");
first.onchange = function() {
myWorker.postMessage([first.value, second.value]);
console.log('Message posted to worker');
}
second.onchange = function() {
myWorker.postMessage([first.value, second.value]);
console.log('Message posted to worker');
}
myWorker.onmessage = function(e) {
result.textContent = e.data;
console.log('Message received from worker');
}
} else {
console.log('Your browser doesn\'t support web workers.')
}
onmessage = function(e) {
console.log('Worker: Message received from main script');
const result = e.data[0] * e.data[1];
if (isNaN(result)) {
postMessage('Please write two numbers');
} else {
const workerResult = 'Result: ' + result;
console.log('Worker: Posting message back to main script');
postMessage(workerResult);
}
}
(2)共享Worker
代碼示例:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport" content="width=device-width">
<title>Shared Workers basic example</title>
<link rel="stylesheet" href="style.css">
<!--[if lt IE 9]>
<script src="http://html5shiv.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
</head>
<body>
<h1>Shared<br>Workers<br>basic<br>example</h1>
<div class="controls" tabindex="0">
<form>
<div>
<label for="number1">Multiply number 1: </label>
<input type="text" id="number1" value="0">
</div>
<div>
<label for="number2">Multiply number 2: </label>
<input type="text" id="number2" value="0">
</div>
</form>
<p class="result1">Result: 0</p>
<p><a href="index2.html" target="_blank">Go to second worker page</a></p>
</div>
<script src="multiply.js"></script>
<script src="nosubmit.js"></script>
</body>
</html>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport" content="width=device-width">
<title>Shared Workers basic example</title>
<link rel="stylesheet" href="style.css">
<!--[if lt IE 9]>
<script src="http://html5shiv.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
</head>
<body>
<h1>Shared<br>Workers<br>basic<br>example</h1>
<div class="controls" tabindex="0">
<form>
<div>
<label for="number3">Square number: </label>
<input type="text" id="number3" value="0">
</div>
</form>
<p class="result2">Result: 0</p>
</div>
<script src="square.js"></script>
<script src="nosubmit.js"></script>
</body>
</html>
var first = document.querySelector('#number1');
var second = document.querySelector('#number2');
var result1 = document.querySelector('.result1');
if (!!window.SharedWorker) {
var myWorker = new SharedWorker("worker.js");
first.onchange = function() {
myWorker.port.postMessage([first.value, second.value]);
console.log('Message posted to worker');
}
second.onchange = function() {
myWorker.port.postMessage([first.value, second.value]);
console.log('Message posted to worker');
}
myWorker.port.onmessage = function(e) {
result1.textContent = e.data;
console.log('Message received from worker');
console.log(e.lastEventId);
}
}
var form = document.querySelector('form');
form.onsubmit = function(e) {
e.preventDefault();
};
onconnect = function(e) {
var port = e.ports[0];
port.onmessage = function(e) {
var workerResult = 'Result: ' + (e.data[0] * e.data[1]);
port.postMessage(workerResult);
}
}
onconnect = function(e) {
var port = e.ports[0];
port.onmessage = function(e) {
var workerResult = 'Result: ' + (e.data[0] * e.data[1]);
port.postMessage(workerResult);
}
}