--- title: Web-Workers date: 2019-09-21 19:22:06 tags: - JavaScript - Web Worker categories: - JavaScript --- 在较早的时候,JavaScript本身是没有多线程的,如果执行耗时长的同步操作会造成页面假死影响用户体验 HTML5提供了`Web Workers API`,可以解决这个问题 并非所有耗时操作都可以用异步解决 比如密集的计算或者一些高延迟的任务 简单来说,Web Workers可以在一个子线程当中运行一个js脚本,这个js脚本当中可能有一些耗时的操作 从而保证主线程不会被阻塞 并且提供了一些机制在主子线程之间进行通信 ### 基本用法 ```javascript var wk = new Worker('./worker.js', {}) ``` > 第一个参数是worker将执行的脚本的URL,它必须遵守同源策略。 第二个参数是配置项(可选),有以下三个有效项 + type:指定要创建的工作进程类型的名称。如果未指定,则默认使用classic。 + credentials:指定要用于工作进程的凭据类型的名称。如果未指定,或者类型为classic,则使用的默认值为省略(不需要凭据)。 + name:一个字符串,指定表示工作进程作用域的专用WorkerGlobalScope的标识名称,主要用于调试。 ### 线程通信 不同的线程之间的通信是采用事件机制触发的,使用`message`事件进行通信 ```javascript // main.js console.log('这里是主线程') var wk = new Worker('./js/worker.js', {}) wk.onmessage = event => { // 这里用来接收子线程发过来的消息 console.log(event.data) } wk.onerror = err => { // 子线程抛出的错误 wk.terminate() // 立即终止worker console.error(err) } let btn = document.getElementById('send-msg') btn.addEventListener('click', function(){ // 向子线程发送消息 wk.postMessage({msg: 'HOHO~'}) // 可以是任意的对象 }) console.log('这里还是主线程') ``` 子线程对应的js代码 ```javascript // worker.js console.log('这里是子线程') let i = 1 function simpleCount() { self.postMessage(++i) if(i>100) { self.close() // 子线程关闭自己 } setTimeout(simpleCount, 1000) } simpleCount() self.onmessage = event => { console.log(event.data) } ``` + 子线程中的`self`代表线程自身,如同非严格模式下主线程中this指向window 非严格模式下的子线程this指向这个self + 子线程无法读取主线程所在网页的DOM对象,也无法使用document、window、parent这些对象。 但是,子线程可以`navigator`对象和`location(只读)`对象 + postMessage的对象传递是`值拷贝`,而且是深拷贝,并非地址拷贝 (实际实现的方式是先进行序列化,再进行反序列化) + 子线程中有可以加载脚本的函数 **importScripts('script1.js', 'script2.js')** + 子线程中不能执行诸如**alert confirm**等阻塞性的函数 + 子线程执行**close**或者主线程对worker执行**terminate**会结束子线程 Worker对象如果没有引用指向,则会被JavaScript的垃圾回收器回收 其他情况下子线程即使空闲也不会停止,随时可以响应主线程的通信 + `wk.onmessage`同样也可以使用`wk.addEventListener('message', function(){...})` ### 数据转移 主线程与子线程之间可以交换二进制数据,比如ArrayBuffer对象 但是拷贝方式发送数据会造成严重的性能问题 因此postMessage可以接受第二个参数,用来把二进制数据直接转移给子线程 (一旦转移,主线程将无法再使用这些数据) ```javascript let obj = { ab: new ArrayBuffer(10) } console.log('转交前长度', obj.ab.byteLength) // 10 wk.postMessage(obj,[obj.ab]) console.log('转交后长度', obj.ab.byteLength) // 0 ``` 在子线程当中同样通过event.data拿到该对象 ```javascript self.onmessage = function(event) { console.log(event.data.ab) } ``` > 可以这样转移的对象须实现`Transferable`接口,这只是一个标记接口 实现该接口的类型有`ArrayBuffer`、`MessagePort`、`ImageBitmap` ### 可能的错误 + 如果文档不允许启动worker,则会引发`SecurityError` + 如果脚本之一的MIME类型为 text/csv, image/\*, video/\*,或 audio/\*, 则会引发`NetworkError`。它应该始终是 text/javascript。 + 如果url无法解析,则引发`SyntaxError`。 ### 在webpack中使用 需要引入`work-loader`这个加载器 ```javascript let MyWorker = require('worker-loader!./test.js') // 引入的这个对象可以直接当做Worker对象来用 let wk = new MyWorker() wk.postMessage('test') ``` 或者在webpack的配置文件当中添加规则 ```javascript { module: { rules: [ { // 匹配 *.worker.js test: /\.worker\.js$/, use: { loader: 'worker-loader', options: { name: '[name]:[hash:8].js', // inline: true, // fallback: false // publicPath: '/scripts/workers/' } } } ] } } ``` #### 同源策略 Web Worker严格遵守同源策略,如果webpack的静态资源与应用代码不是同源的,那么很有可能就被浏览器给墙掉了,而且这种场景也经常发生。 对于Web Worker遇到这种情况,有两种解决方案 1. 通过设置worker-loader的选项参数`inline`把worker内联成blob数据格式,而不再是通过下载脚本文件的方式来使用worker 2. 通过设置worker-loader的选项参数`publicPath`来重写掉worker脚本的下载url,当然脚本也要存放到同样的位置