<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Web Worker</title>
<script src="https://cdn.bootcss.com/vue/2.2.2/vue.min.js"></script>
</head>
<body>
<div>
<p>测试说明,打开调试工具的 console 的 Tab,在 input 中输入你需要求和的数字,点击 Computed 按钮即可观察到测试数据,你可以多试几次才能让你更好的理解 WebWorker 的工作原理。</p>
<input type="number" v-model="num" />
<button @click="computed">Computed</button>
</div>
<script src="./js/cube.js"></script>
<script src="./js/test.js"></script>
</body>
</html>
let myWorker
if(window.Worker){
// todo
myWorker = new Worker('./js/worker.js')
} else {
// 不支持web Worker
alert('不支持web Worker')
}
let app = new Vue({
el: '#app',
data: {
num: 1000000,
},
methods: {
computed () {
console.log(`Message posted to worker=${this.num}`);
myWorker.postMessage(this.num)
// 在主线程中终止Worker
// myWorker.terminate();
}
}
})
myWorker.onmessage = (e) => {
console.log(e.data+'main.js')
}
onmessage = function(e) {
let num1 = e.data;
let num2 = 0;
console.time('time')
for(let i = 0; i < num1; i++){
num2 += i;
}
console.timeEnd('time')
console.log(`${num2}worker.js`)
// 当我们计算出结果,应该回传
postMessage(num2);
// 在Worker中终止自身
//close();
}
Web Workers 使得一个 Web 应用程序可以在与主执行线程分离的后台线程中运行一个脚本操作。这样做的好处是可以在一个单独的线程中执行费时的处理任务,从而允许主(通常是 UI)线程运行而不被阻塞 / 放慢。
局限性:在 worker 内不能直接操作 DOM 节点,或者使用 window 对象的默认方法和属性。
if(window.Worker){
// todo
} else {
// 不支持web Worker
}
PS:假设页面为 index.html,页面 js 为 main.js,这里的 path 是相对于 index.html 到该 worker.js。
var myWorker = new Worker('worker.js');
PS:假设页面为 index.html,页面主线程 js 为 main.js,这里的 path 是相对于 index.html 到该 worker.js。
let myWorker
if(window.Worker){
// todo
myWorker = new Worker('./js/worker.js')
} else {
// 不支持web Worker
alert('不支持web Worker')
}
let app = new Vue({
el: '#app',
data: {
num: 1000000,
},
methods: {
computed () {
console.log(`Message posted to worker=${this.num}`);
// 重点在这里,向 myWorker 发送我们 input 中的值,让它为去做耗时的计算
myWorker.postMessage(this.num)
}
}
})
PS:如果想发送多个消息,可以这样 myWorker.postMessage([msg1, msg2…]),对应接收的 e.data 对象也就是一个数组了,若是对象的话需要序列化,接收的时候需要反序列化。
// 有数据发过来,就触发
onmessage = function(e) {
let num1 = e.data;
let num2 = 0;
console.time('计算耗时')
for(let i = 0; i < num1; i++){
num2 += i;
}
console.timeEnd('计算耗时')
console.log(`Worker 计算结果=${num2}`)
// 当我们计算出结果,应该回传
postMessage(num2);
}
我向一个 Worker 发送一个较大 num,然后求出该1到 num 的整数和。在页面中 input 的值分别为:1000000、10000000、100000000 各执行了一次计算,最后一次花费了 11s 左右(算的上耗时计算了吧)。就这么简单,我们就实现了主线程 js 和 WebWorker 的双向通信。
如果你需要从主线程中立刻终止一个运行中的 worker,可以调用 worker 的 terminate 方法:
myWorker.terminate();
worker 线程会被立即杀死,不会有任何机会让它完成自己的操作或清理工作。
close()
当然我们刚刚仅仅考虑了正常情况,还有需要错误等待我们处理呢?当 worker 出现运行中错误时,它的 onerror 事件处理函数会被调用。它会收到一个扩展了 ErrorEvent 接口的名为 error 的事件。
该事件不会冒泡并且可以被取消;为了防止触发默认动作,worker 可以调用错误事件的 preventDefault() 方法。错误事件有以下三个用户关心的字段:
message:可读性良好的错误消息。
filename:发生错误的脚本文件名。
lineno:发生错误时所在脚本文件的行号。
如果需要的话 worker 能够生成更多的 worker。这就是所谓的subworker,它们必须托管在同源的父页面内。
而且,subworker 解析 URI 时会相对于父 worker 的地址而不是自身页面的地址。这使得 worker 更容易记录它们之间的依赖关系。
Worker 线程能够访问一个全局函数importScripts()来引入脚本,该函数接受0个或者多个URI作为参数来引入资源。
importScripts();
importScripts('cube.js');
importScripts('cube1.js', 'cube2');
一个共享 worker 可以被多个脚本使用——即使这些脚本正在被不同的 window、iframe 或者 worker 访问。由于 SharedWorker 与 专有 Worker 非常相似,这里我只是提一下它们的区别。
读者若需要做测试的话,可以考虑在 2 个 html 页面中的 javascript 代码使用的是同一个 worker。
var myWorker = new SharedWorker('worker.js');
// 父级线程中的调用
myWorker.port.start();
// worker线程中的调用, 假设port变量代表一个端口
port.start();
一个非常大的区别在于,与一个共享 worker 通信必须通过端口对象——一个确切的打开的端口供脚本与 worker 通信(在专用 worker 中这一部分是隐式进行的)。
在使用 start() 方法打开端口连接时,如果父级线程和 worker 线程需要双向通信,那么它们都需要调用 start() 方法。
改写我们的 computed 方法(vue 组件中)
computed () {
console.log(`Message posted to worker=${this.num}`);
// 重点在这里,向 myWorker 发送我们 input 中的值,让它为去做耗时的计算
myWorker.port.postMessage(this.num)
}
onconnect = function(e) {
var port = e.ports[0];
port.onmessage = function(e) {
// 同样e.data为主线程发送的数据
console.log(e.data)
//复杂的计算
let result = e.data*1000*23*3
// Worker需要回传至主线程
port.postMessage(result);
}
}
myWorker.port.onmessage = function(e) {
result2.textContent = e.data;
console.log('Message received from worker');
}
PS:总结差异,主线程和 Worker 都要执行 start(),通信时需要带上 port。
Worker接口会生成真正的操作系统级别的线程,如果你不太小心,那么并发(concurrency)会对你的代码产生有趣的影响。然而,对于 web worker 来说,与其他线程的通信点会被很小心的控制,这意味着你很难引起并发问题。
CSP 全称 Content Security Policy 为了页面内容安全而制定的一系列防护策略,通过 CSP 所约束的的规责指定可信的内容来源(这里的内容可以指脚本、图片、iframe、fton、style 等等可能的远程的资源)。
可以限制如下资源的加载:
script-src:外部脚本
style-src:样式表
img-src:图像
media-src:媒体文件(音频和视频)
font-src:字体文件
object-src:插件(比如 Flash)
child-src:框架
frame-ancestors:嵌入的外部资源(比如<frame>、<iframe>、<embed>和<applet>)
connect-src:HTTP 连接(通过 XHR、WebSockets、EventSource等)
worker-src:worker脚本
manifest-src:manifest 文件
除了 Content-Security-Policy,还有一个 Content-Security-Policy-Report-Only 字段,表示不执行限制选项,只是记录违反限制的行为。但它必须与report-uri选项配合使用。
Content-Security-Policy-Report-Only: default-src 'self'; ...; report-uri /my_amazing_csp_report_parser;
<meta http-equiv="Content-Security-Policy" content="script-src 'self'; object-src 'none'; style-src cdn.example.org third-party.org; child-src https:">
以下语句设置在请求头部(Header)
Content-Security-Policy "default-src 'self';"
如果要为 Worker 指定 CSP 策略,可以为 Worker 脚本的请求的响应的头部设置 CSP 策略。这时这个 Worker 会继承它所属的文档或者创建它的 Worker 的 CSP 策略。
在主页面与 worker 之间传递的数据是通过拷贝,而不是共享来完成的。传递给 worker 的对象需要经过序列化,接下来在另一端还需要反序列化。

