一、概述
1、介绍
“JavaScript是单线程的”这一说法它事实上描述了JavaScript环境在浏览器内的一般行为。
这样的单线程范例本身是有局限的,因为它妨碍了在语言中其他可行的编程模式,否则可将工作委托给独立的线程或进程。JavaScript被限定到这一单线程范例上,是为了让它和各种浏览器API交互时保持兼容性。例如DOM如果被多个JavaScript线程同时修改会出现问题。因此,不能使用传统的并发结构增强JavaScript。
Web Worker 的作用,就是为 JavaScript 创造多线程环境,允许主线程创建 Worker 线程,将一些任务分配给后者运行。在主线程运行的同时,Worker 线程在后台运行,两者互不干扰。等到 Worker 线程完成计算任务,再把结果返回给主线程。这样的好处是,一些计算密集型或高延迟的任务,被 Worker 线程负担了,主线程(通常负责 UI 交互)就会很流畅,不会被阻塞或拖慢。
workers的核心观点是:允许主执行线程委托工作给独立的实体,而不用改变现存的单线程模型。
2、Web Worker与线程的比较
相同特性:
- workers是用实际的线程实现的。例如Blink浏览器引擎使用WorkerTread实现workers,它对应一个底层的平台线程。
- workers并行执行。尽管页面和worker的实现都是单线程JavaScript环境,各个环境的指令都是并行执行的。
- workers可以共享一些内存。workers可以使用SharedArrayBuffer 在多环境中共享内存。线程使用锁来实现并行控制,而JavaScript使用Atomics接口实现并行控制。
不同点:
- workers不会共享所有内存。在传统的线程模型中,多线程有能力读写共享内存空间。除了sharedArrayBuffer之外,在workers移动数据的进出需要对数据复制或者转存。
- worker线程不必是同一进程的一部分。通常,单个进程可以在它内部生成多个线程。取决于浏览器引擎的实现方法,worker线程可以是,也可以不是页面进程的一部分。例如Chrome的Blink引擎使用独立进程实现共享worker和服务worker线程。
- worker线程的创建成本更昂贵。worker线程包括自己的独立事件循环,全局对象,事件处理程序,和其他JavaScript环境中的部分特性。创建这些线程的计算开销不可忽视。
注意:在形式和功能上,workers都不是线程的简单替代。
workers相对更重,不建议大量使用。例如,对四百万像素的图片的每个像素都启动一个worker是不合适的。一般来说,我们预期workers是持久性的,有较高的启动性能花销,和较高的每实例内存开销。所以,web worker有利于随时响应主线程的通信。但是,这也造成了 Worker 比较耗费资源,不应该过度使用,而且一旦使用完毕,就应该关闭。
二、Web Worker
1、Workers类型
(1)专用Worker
专用web worker,一般也称为专用worker,web worker,或就叫worker,是允许脚本生成独立JavaScript线程并委托任务的基本应用。专用worker正如它的名字所表明的,只能由创建它的页面访问。
(2)共享Worker
共享web worker的行为很像专用worker。主要的区别是共享worker可以让多个环境访问,包括不同的页面。任意脚本执行所在的源如果与最初生成共享worker的脚本所在的源相同,就可以发送和接受共享worker的消息。
(3)其他
- ServiceWorkers (服务worker)一般作为web应用程序、浏览器和网络(如果可用)之前的代理服务器。它们旨在(除开其他方面)创建有效的离线体验,拦截网络请求,以及根据网络是否可用采取合适的行动并更新驻留在服务器上的资源。他们还将允许访问推送通知和后台同步API。
- Chrome Workers 是一种仅适用于firefox的worker。如果您正在开发附加组件,希望在扩展程序中使用worker且有在你的worker中访问 js-ctypes 的权限,你可以使用Chrome Workers。详情请参阅
ChromeWorker
。 - Audio Workers (音频worker)使得在web worker上下文中直接完成脚本化音频处理成为可能。
2、使用web worker的注意事项
###(1)同源限制
分配给 Worker 线程运行的脚本文件,必须与主线程的脚本文件同源。
###(2)DOM 限制
Worker 线程所在的全局对象,与主线程不一样,无法读取主线程所在网页的 DOM 对象,也无法使用document、window、parent这些对象。但是,Worker 线程可以navigator对象和location对象。
###(3)通信联系
Worker 线程和主线程不在同一个上下文环境,它们不能直接通信,必须通过消息完成。
###(4)脚本限制
Worker 线程不能执行alert()方法和confirm()方法,但可以使用 XMLHttpRequest 对象发出 AJAX 请求。
####(5)文件限制
Worker 线程无法读取本地文件,即不能打开本机的文件系统(file://),它所加载的脚本,必须来自网络。
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="//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="//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="//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);
}
}