blog-web/source/_posts/JavaScript/Web-Workers.md
2019-09-22 01:15:33 +08:00

147 lines
5.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
title: Web-Workers
date: 2019-09-21 19:22:06
tags:
- JavaScript
- Web Worker
categories:
- JavaScript
---
在较早的时候JavaScript本身是没有多线程的如果执行耗时长的同步操作会造成页面假死影响用户体验
HTML5提供了`Web Workers API`,可以解决这个问题
<!-- more -->
并非所有耗时操作都可以用异步解决
比如密集的计算或者一些高延迟的任务
简单来说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当然脚本也要存放到同样的位置