ES6+Webpack下使⽤WebWorker
⼤家都知道 HTML 5 新增了很多 API,其中就包括 Web Worker,在普通的 js ⽂件上使⽤ ES5 编写相关代码应该是完全没有问题了,只需要在⽀持 H5 的浏览器上就能跑起来。
那如果我们需要在 ES6+Webpack 的组合环境下使⽤ Web Worker呢?其实也很⽅便,只需要注意⼀下个别点,接下来记录⼀下我踩过的坑。ajax实例里面的函数
⾄于 Web Worker 的基础知识和基本 api 我就放到最后⾯当给还不了解或者没有系统使⽤过的读者们去简单阅读⼀下。
1. 快速创建⼯程环境
假设你已经有⼀份 ES6+Webpack 的代码⼯程环境,⽽且是可以顺利跑起来的;如果没有,可以 clone 我的 github 仓库:
2. 安装及使⽤ worker-loader
2.1 安装依赖:
$ npm install -D worker-loader
# 或
$ yarn add worker-loader --dev
复制代码
2.2 代码中直接使⽤ worker-loader
// main.js
var MyWorker = require("worker-loader!./file.js");
// var MyWorker = require("worker-loader?inline=true&fallback=false!./file.js");
var worker = new MyWorker();
worker.postMessage({a: 1});
worker.addEventListener("message", function(event) { /* 操作 */ });
复制代码
优点:写 worker 逻辑的脚本⽂件可以任意命名,只要传进worker-loader中处理即可;缺点:每引⼊⼀次 worker 逻辑的脚本⽂件,就需要写⼀次如上所⽰的代码,需要多写 N(N>=1) 次的 "worker-loader!"
2.3 在 webpack 的配置⽂件中引⼊worker-loader
{
module: {
rules: [
{
// 匹配 *.worker.js
test: /\.worker\.js$/,
use: {
loader: 'worker-loader',
options: {
name: '[name]:[hash:8].js',
// inline: true,
// fallback: false
// publicPath: '/scripts/workers/'
}
}
}
]
}
}
复制代码
其中配置,可以设置inline属性为true将 worker 作为 blob 进⾏内联;要注意,内联模式将额外为浏览器创建chunk,即使对于不⽀持内联worker 的浏览器也是如此;若这种浏览器想要禁⽤这种⾏为,只需要将fallback参数设置为false即可。
3. 同源策略
Web Worker 严格遵守同源策略,如果 webpack 的静态资源与应⽤代码不是同源的,那么很有可能就被浏览器给墙掉了,⽽且这种场景也经常发⽣。对于 Web Worker 遇到这种情况,有两种解决⽅案。
3.1 第⼀种
通过设置worker-loader的选项参数inline把 worker 内联成 blob 数据格式,⽽不再是通过下载脚本⽂件的⽅式来使⽤ worker:
import Worker from './file.worker.js';
复制代码
{
loader: 'worker-loader'
options: { inline: true }
}
复制代码
3.2 第⼆种
通过设置worker-loader的选项参数publicPath来重写掉 worker 脚本的下载 url,当然脚本也要存放到同样的位置:
App.js
// This will cause the worker to be downloaded from `/workers/file.worker.js`
import Worker from './file.worker.js';
复制代码
{
loader: 'worker-loader'
options: { publicPath: '/workers/' }
}
复制代码
4. devServer 模式下报错 "window is not defined"
若使⽤了webpack-dev-server启动了本地调试服务器,则有可能会在控制台报错: "Uncaught ReferenceError: window is not defined"
反正我是遇到了,了很久未果,当时还是洗了把脸冷静下来排查问题,尝试着先后在worker-loader、webpack-dev-server和webpack的 github 仓库的 issues ⾥⾯去,最后果然在webpack的 github 仓库⾥到了码友的提问,官⽅给出了答案:
只需要在 webpack 的配置⽂件下的 output 下,加⼀个属性对:globalObject: 'this'
output: {
path: DIST_PATH,
publicPath: '/dist/',
filename: '[name].bundle.[hash:8].js',
chunkFilename: "[name].chunk.[chunkhash:8].js",
globalObject: 'this',
},
复制代码
5. Web Worker 出现的背景
JavaScript 引擎是单线程运⾏的,JavaScript 中耗时的 I/O 操作都被处理为异步操作,它们包括键盘、
⿏标 I/O 输⼊输出事件、窗⼝⼤⼩的resize事件、定时器(setTimeout、setInterval)事件、Ajax 请求⽹络 I/O 回调等。当这些异步任务发⽣的时候,它们将会被放⼊浏览器的事件任务队列中去,等到 JavaScript 运⾏时执⾏线程空闲时候才会按照队列先进先出的原则被⼀⼀执⾏,但终究还是单线程。
平时看似够⽤的异步编程(promise、async/await),在遇到很复杂的运算,⽐如说图像的识别优化或转换、H5游戏引擎的实现,加解密算法操作等等,它们的不⾜就将逐渐体现出来。长时间运⾏的 js 进程会导致浏览器冻结⽤户界⾯,降低⽤户体验。那有没有什么办法可以将复杂的计算从业务逻辑代码抽离出来,让计算运⾏的同时不阻塞⽤户操作界⾯获得反馈呢?
HTML5 标准通过了 Web Worker 的规范,该规范定义了⼀套 api,它允许⼀段 js 程序运⾏在主线程之外的另⼀个线程中。⼯作线程允许开发⼈员编写能够长时间运⾏⽽不被⽤户所中断的后台程序,去执⾏事务或者逻辑,并同时保证页⾯对⽤户的及时响应,可以将⼀些⼤量计算的代码交给web worker运⾏⽽不冻结⽤户界⾯。
5. Web Worker 的类型
之前⼀直认为不就那⼀种类型吗,哪⾥还会有多类型的 Worker。答案是有的,其可分为两种类型:
1. 专⽤ Worker, Dedicated Web Worker
2. 共享 Worker, Shared Web Worker
「专⽤ Worker」只能被创建它的页⾯访问,⽽「共享 Worker」可以在浏览器的多个标签中打开的同⼀个页⾯间共享。
在 js 代码中,Woker类代表Dedicated Worker;Shared Worker类代表Shared Web Worker。
下⾯的⼀些⽰例代码我就直接⽤ ES5 去写了,上⾯教了⼤家怎么使⽤在 ES6+Webpack 的环境下,迁移这种⼯作⼤家就当练习,多动动
6. 如何创建 Worker
很简单
// 应⽤⽂件 app.js
var worker = new Worker('./my.worker.js'); // 传⼊ worker 脚本⽂件的路径即可
复制代码
7. 如何与 worker 通信
就通过两个⽅法即可完成:
应⽤⽂件 app.js
// 创建 worker 实例
var worker = new Worker('./my.worker.js'); // 传⼊ worker 脚本⽂件的路径即可
// 监听消息
// 主线程收到⼯作线程的消息
};
// 主线程向⼯作线程发送消息
worker.postMessage({
value: '主线程向⼯作线程发送消息'
});
复制代码
worker ⽂件 my.worker.js
// 监听消息
// ⼯作线程收到主线程的消息
};
this.postMessage({
value: '⼯作线程向主线程发送消息'
});
复制代码
8. Worker 的全局作⽤域
使⽤ Web Worker 最重要的⼀点是要知道,它所执⾏的 js 代码完全在另⼀作⽤域中,与当前主线程的代码不共享作⽤域。在 Web Worker 中,同样有⼀个全局对象和其他对象以及⽅法,但其代码⽆法访问 DOM,也不能影响页⾯的外观。
Web Worker 中的全局对象是 worker 对象本⾝,也即this和self引⽤的都是 worker 对象,说⽩了,就像上⼀段在my.worker.js的代码,this完全可以换成self,甚⾄可以省略。
为便于处理数据,Web Worker 本⾝也是⼀个最⼩化的运⾏环境,其可以访问或使⽤如下数据:
最⼩化的navigator对象包括onLine, appName, appVersion, userAgent和platform属性
只读的location对象
setTimeout(), setInterval(), clearTimeout(), clearInterval()⽅法
XMLHttpRequest构造函数
9. 如何终⽌⼯作线程
如果在某个时机不想要 Worker 继续运⾏了,那么我们需要终⽌掉这个线程,可以调⽤在主线程 Worker 的terminate⽅法或者在相应的线程中调⽤close⽅法:
应⽤⽂件 app.js
var worker = new Worker('./worker.js');
// ...⼀些操作
复制代码
Worker ⽂件 my.worker.js
self.close();
复制代码
10. Worker 的错误处理机制
具体来说,Worker 内部的 js 在执⾏过程中只要遇到错误,就会触发error事件。发⽣error事件时,事件对象中包含三个属性:filename, lineno
和message,分别表⽰发⽣错误的⽂件名、代码⾏号和完整的错误消息。
worker.addEventListener('error', function (e) {
console.log('MAIN: ', 'ERROR', e);
console.log('filename:' + e.filename + '-message:' + e.message + '-lineno:' + e.lineno);
});
复制代码
11. 引⼊脚本与库
Worker 线程能够访问⼀个全局函数importScripts()来引⼊脚本,该函数接受 0 个或者多个 URI 作为参数来引⼊资源;以下例⼦都是合法的:importScripts();                        /* 什么都不引⼊ */
importScripts('foo.js');                /* 只引⼊ "foo.js" */
importScripts('foo.js', 'bar.js');      /* 引⼊两个脚本 */
复制代码
浏览器加载并运⾏每⼀个列出的脚本。每个脚本中的全局对象都能够被 worker 使⽤。如果脚本⽆法加载,将抛出NETWORK_ERROR异常,接下来的代码也⽆法执⾏。⽽之前执⾏的代码(包括使⽤window.setTimeout()异步执⾏的代码)依然能够运⾏。importScripts()之后的函数声明依然会被保留,因为它们始终会在其他代码之前运⾏。
注意:脚本的下载顺序不固定,但执⾏时会按照传⼊importScripts()中的⽂件名顺序进⾏。这个过程是同步完成的;直到所有脚本都下载并运⾏完毕,importScripts()才会返回。

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。