web worker 是Web应用程序在独立于主线程的后台线程中,运行一个脚本的操作。使用构造函数(例如,Worker())创建一个 worker 对象, 构造函数接受一个 JavaScript文件URL , 这个文件包含了将在 worker 线程中运行的代码脚本。
主线程和 worker 线程相互之间使用 postMessage() 方法来发送信息, 并且通过 onmessage这个 event handler来接收信息(传递的信息包含在 Message 这个事件的data属性内) 。数据的交互方式为传递副本,而不是直接共享数据。
worker 将运行在与当前 window不同的另一个全局上下文中,这个上下文由一个对象表示,标准情况下为DedicatedWorkerGlobalScope
在浏览器中javascript的执行是单线程的,一个线程一次只能做一件事情,这样必然会出现阻塞的情况,非常影响用户体验,所以ajax应运而生了。ajax的出现使得页面在等待服务器响应的这段时间内不再发生阻塞,但是这仍然没有改变代码单线程执行的本质,这也意味着我们依旧不能把耗费时间的复杂运算放在页面上执行。而web worker的出现弥补了这个缺点。
web worker为 JavaScript 创造多线程环境,允许主线程通过new运算符创建 Worker 线程(子线程),将一些任务分配给Worker线程,这样一些计算量大的、耗费时间的、复杂的运算都可以放到worker线程上,避免主线程被阻塞。
myworker.postMessage()方法的参数,就是主线程传给 Worker 的数据。它可以是各种数据类型,包括二进制数据。
//主线程
let myworker = new Worker('worker.js');
this.myWorker.postMessage([value1, value2])
子线程接收到主线程传递过来的条件参数,将经过处理的数据通过postMessage(Data)传递给主线程,
//子线程
onmessage = function(e) {
console.log(e.data, '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)
}
}
接着,主线程通过myworker.onmessage指定监听函数,这样主线程就接收到子线程发回来的消息了
//主线程
this.myWorker.onmessage = function (e) {
result.textContent = e.data
console.log('Message received from worker')
}
主线程接收到经过子线程处理过的数据之后可以关闭子线程:
this.myworker.terminate()
worker线程中有一个监听函数,监听message事件,以下写法二和写法三中self和this都可以,worker 将运行在与当前 window不同的另一个全局上下文中,这个上下文由一个对象表示,标准情况下为DedicatedWorkerGlobalScope,self和this都指向这个对象,(标准 workers 由单个脚本使用; 共享workers使用SharedWorkerGlobalScope)
写法一:
onmessage = function(e) {
postMessage('You said: ' + e.data);
}
写法二:
self.addEventListener('message', function(e) {
postMessage('You said: ' + e.data);
})
写法三:
this.addEventListener('message', function(e) {
postMessage('You said: ' + e.data);
})
写法四:
addEventListener('message', function (e) {
postMessage('You said: ' + e.data);
}, false);
根据主线程发来的数据,Worker 线程可以调用不同的方法,下面是一个例子。
self.addEventListener('message', function (e) {
var data = e.data;
switch (data.cmd) {
case 'start':
self.postMessage('WORKER STARTED: ' + data.msg);
break;
case 'stop':
self.postMessage('WORKER STOPPED: ' + data.msg);
self.close(); // Terminates the worker.
break;
default:
self.postMessage('Unknown command: ' + data.msg);
};
}, false);
上面代码中,self.close()用于在 Worker 内部关闭自身。
importScripts('script1.js');
可同时加载多个脚本
importScripts('script1.js', 'script2.js');
主线程可以监听 Worker 是否发生错误。如果发生错误,Worker 会触发主线程的error事件。
worker.onerror(function (event) {
console.log([
'ERROR: Line ', e.lineno, ' in ', e.filename, ': ', e.message
].join(''));
});
// 或者
worker.addEventListener('error', function (event) {
// ...
});
Worker 内部也可以监听error事件。
2.5 关闭 Worker
使用完毕,为了节省系统资源,必须关闭 Worker。
// 主线程
worker.terminate();
// Worker 线程
self.close();
以下例子在vue项目中使用如下:
webWorker.vue
<template>
<div tabindex="0">
<h1>Web Workers</h1>
<form>
<div>
<label for="number1">number 1: </label>
<input type="text" id="number1" :value="inputValue1" @change="handleChange1">
</div>
<div>
<label for="number2">number 2: </label>
<input type="text" id="number2" :value="inputValue2" @change="handleChange2">
</div>
</form>
<p class="result">Result: 0</p>
</div>
</template>
<script>
export default {
data () {
return {
inputValue1: 0,
inputValue2: 0
}
},
methods: {
workerFun (value1, value2, type) {
const result = document.querySelector('.result')
if (window.Worker) {
this.myWorker = new Worker('worker.jsx')
this.myWorker.postMessage([value1, value2])
console.log('Message posted to worker', type)
this.myWorker.onmessage = function (e) {
result.textContent = e.data
this.myworker.terminate()
console.log('Message received from worker')
}
} else {
console.log('Your browser doesn\'t support web workers.')
}
},
handleChange1 (e) {
this.inputValue1 = e.target.value
this.workerFun(e.target.value, this.inputValue2, 'first')
},
handleChange2 (e) {
this.inputValue2 = e.target.value
this.workerFun(this.inputValue1, e.target.value, 'second')
}
}
}
</script>
worker.jsx放到根目录下面,代码如下:
onmessage = function(e) {
console.log(e.data, '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)
}
}
(1)同源限制
分配给 Worker 线程运行的脚本文件,必须与主线程的脚本文件同源。
(2)DOM 限制
Worker 线程所在的全局对象,与主线程不一样,无法读取主线程所在网页的 DOM 对象,也无法使用document、window、parent这些对象。但是,Worker 线程可以navigator对象和location对象。
(3)通信联系
Worker 线程和主线程不在同一个上下文环境,它们不能直接通信,必须通过消息完成。
(4)脚本限制
Worker 线程不能执行alert()方法和confirm()方法,但可以使用 XMLHttpRequest 对象发出 AJAX 请求。
(5)文件限制
Worker 线程无法读取本地文件,即不能打开本机的文件系统(file://),它所加载的脚本,必须来自网络。
一般情况下,很少会用到web Worker,返回到浏览器到数据在后台已经经过计算处理,不太可能让前端处理复杂到业务逻辑
webWorker解决的是js中数据处理导致的UI线程阻塞,所以webWorker的应用场景一般为需要大量计算的时候,这将避免ui界面渲染被阻塞
大数据的处理:
这里所说的大数据处理,并不是指数据量非常大,而是要从计算量来看,通常用时不能控制在毫秒级内的运算都可以考虑放在web worker中执行。
高频的用户交互:
高频的用户交互适用于根据用户的输入习惯、历史记录以及缓存等信息来协助用户完成输入的纠错、校正功能等类似场景,用户频繁输入的响应处理同样可以考虑放在web worker中执行。
最后声明一点,了解后台的同学千万不要认为web worker等同于后台意义的多线程,web worker现在有了多线程的形(有了多线程的用法),但还不具备多线程的神(不具备线程通信、锁等后台线程的基本特性),web worker的本质是支持我们把数据刷新与页面渲染两个动作拆开执行(不使用web worker的话这两个动作在主线程中是线性执行的)。
为了防止主线程造成拥堵,可以开启worker线程,这样不影响主线程的数据加载,可以在worker中进行数据请求,也可以创建多个worker线程,不过Web worker也有一些使用限制:无法访问DOM节点,无法访问全局变量或是全局函数。所以之前用的是jQuery Ajax发送请求的,这里就不能用了;只能用原生的ajax请求啦。
self.addEventListener('message', function(e) {
const result = e.data[0] * e.data[1]
if (isNaN(result)) {
postMessage('Please write two numbers')
} else {
var xhr = new XMLHttpRequest()
let ajaxResult
xhr.onreadystatechange = function() {
if (xhr.readyState === 4) {
if (xhr.status >= 200 && xhr.status < 300 || xhr.status === 304) {
ajaxResult = JSON.parse(xhr.responseText)
const workerResult = 'Result: ' + result
postMessage({
workerResult,
ajaxResult: ajaxResult.result
})
}
}
}
xhr.open('get', 'https://geo.datav.aliyun.com/areas_v2/bound/100000_full.json', true)
xhr.send(null)
}
})
如有错误请指出,谢谢。
参考:
http://www.ruanyifeng.com/blog/2018/07/web-worker.html
https://blog.csdn.net/lqlqlq007/article/details/79824122