您当前的位置:首页 > 计算机 > 软件应用 > 浏览器应用

详解浏览器中的 Web Worker 线程

时间:12-14来源:作者:点击数:
<!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 Worker

Web Workers 使得一个 Web 应用程序可以在与主执行线程分离的后台线程中运行一个脚本操作。这样做的好处是可以在一个单独的线程中执行费时的处理任务,从而允许主(通常是 UI)线程运行而不被阻塞 / 放慢。

局限性:在 worker 内不能直接操作 DOM 节点,或者使用 window 对象的默认方法和属性。

Worker 特性检测

    if(window.Worker){
      // todo
    } else {
      // 不支持web Worker
    }

PS:假设页面为 index.html,页面 js 为 main.js,这里的 path 是相对于 index.html 到该 worker.js。

专用 Worker

生成一个专用 worker

var myWorker = new Worker('worker.js');

PS:假设页面为 index.html,页面主线程 js 为 main.js,这里的 path 是相对于 index.html 到该 worker.js。

主线程 js 和 Worker 的通信(数据交互)

主线程 js(main.js 用来生成 myWorker)

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 对象也就是一个数组了,若是对象的话需要序列化,接收的时候需要反序列化。

myWorker 脚本代码

// 有数据发过来,就触发
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,可以调用 worker 的 terminate 方法:

myWorker.terminate();

worker 线程会被立即杀死,不会有任何机会让它完成自己的操作或清理工作。

在 Worker 中终止(自杀)

close()

处理错误

当然我们刚刚仅仅考虑了正常情况,还有需要错误等待我们处理呢?当 worker 出现运行中错误时,它的 onerror 事件处理函数会被调用。它会收到一个扩展了 ErrorEvent 接口的名为 error 的事件。

该事件不会冒泡并且可以被取消;为了防止触发默认动作,worker 可以调用错误事件的 preventDefault() 方法。错误事件有以下三个用户关心的字段:

message:可读性良好的错误消息。
filename:发生错误的脚本文件名。
lineno:发生错误时所在脚本文件的行号。

生成 subworker

如果需要的话 worker 能够生成更多的 worker。这就是所谓的subworker,它们必须托管在同源的父页面内。

而且,subworker 解析 URI 时会相对于父 worker 的地址而不是自身页面的地址。这使得 worker 更容易记录它们之间的依赖关系。

在 Worker 中引入脚本与库

Worker 线程能够访问一个全局函数importScripts()来引入脚本,该函数接受0个或者多个URI作为参数来引入资源。

importScripts();                       
importScripts('cube.js');                
importScripts('cube1.js', 'cube2');

共享 Worker(SharedWorker)

一个共享 worker 可以被多个脚本使用——即使这些脚本正在被不同的 window、iframe 或者 worker 访问。由于 SharedWorker 与 专有 Worker 非常相似,这里我只是提一下它们的区别。

读者若需要做测试的话,可以考虑在 2 个 html 页面中的 javascript 代码使用的是同一个 worker。

生成一个共享 worker

var myWorker = new SharedWorker('worker.js');
// 父级线程中的调用
myWorker.port.start();
// worker线程中的调用, 假设port变量代表一个端口  
port.start();

一个非常大的区别在于,与一个共享 worker 通信必须通过端口对象——一个确切的打开的端口供脚本与 worker 通信(在专用 worker 中这一部分是隐式进行的)。

在使用 start() 方法打开端口连接时,如果父级线程和 worker 线程需要双向通信,那么它们都需要调用 start() 方法。

共享 worker 中消息的接收和发送

主线程发送消息给 Worker

改写我们的 computed 方法(vue 组件中)

computed () {
  console.log(`Message posted to worker=${this.num}`);
  // 重点在这里,向 myWorker 发送我们 input 中的值,让它为去做耗时的计算
  myWorker.port.postMessage(this.num)
}

Worker 接收到消息并处理及回传

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

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 标签

<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 之间传递的数据是通过拷贝,而不是共享来完成的。传递给 worker 的对象需要经过序列化,接下来在另一端还需要反序列化。

方便获取更多学习、工作、生活信息请关注本站微信公众号城东书院 微信服务号城东书院 微信订阅号
推荐内容
相关内容
栏目更新
栏目热门
本栏推荐