一、背景
众所周知,js是单线程的,有时候在处理数据量较大的业务时,会造成线程阻塞,就会影响用户使用体验,出现卡顿等情况。针对这个缺点,H5推出了web worker,使JS支持多线程。
二、优点
web worker 可以创建子线程,子线程会在一个全新的环境中执行,你可以在执行子线程的同时,去做任何事情,即不会影响到主线程的正常执行,也不会影响页面的性能与用户体验。
三、应用场景
- 大图片canvas转base64的时候
- 对大量数据进行加密时
- 读取解析一个较大的json文件
- 在做一些插件时,比如代码的拼写检查,代码高亮,实时文本等
- 处理音视频数据
- 处理一些较大的数组集合
- ...
四、使用
1. 子线程
(1)先创建一个子线程文件:child.js
(2)再监听message事件,以获取主线程传过来的数据
(3)使用postMessage方法回传数据给主线程,以达到通知的目的
// 接收主线程信息
// 这里的this指向子线程本身
this.addEventListener('message', (e) => {
console.log('主线程传过来的数据:', e.data)
postMessage('子线程收到数据了')
})
2. 主线程
(1)先做一个兼容性判断,毕竟有些浏览器不支持web worker
(2)创建子线程实例
(3)向子线程发送数据
(4)监听子线程回传的信息
// 兼容性判断
if (!Worker) throw new Error('当前浏览器不支持多线程')
// 创建子线程,参数为子线程脚本文件地址
const childThread = new Worker('child.js')
// 向子线程发送数据
childThread.postMessage('你好子线程')
// 监听子线程信息
childThread.addEventListener('message', (e) => {
console.log('子线程回传的数据: ', e.data)
})
3. 演示结果
可以看到,输出结果分别来自子线程和主线程
4. 关闭线程
当线程结束时,我们需要手动进行关闭,否则会一直运行在后台
主线程中关闭子线程:子线程实例名称.terminate()
子线程内部关闭自己:this.close()
5. 监听错误处理
当子线程发生错误时,主线程可以通过监听error事件来处理:
子线程实例名称.addEventListener('error', (e) => {...})
6. 子线程中引入文件
有时,我们需要在子线程中引入其他脚本文件:
importScripts('script1.js', 'script2.js')
7. 多线程嵌套
子线程中可以再次通过new Worder('xxx')去实例化一个属于自己的子线程,实现线程嵌套,以处理复杂业务。
五、小试牛刀
我们用for循环来简单模拟处理大量数据的场景,来看看不使用worker与使用worker的情况有何不同
1. 不使用worker的情况
console.log('执行开始')
console.time("执行用时")
let arr = []
for (let i = 0; i < 100000000; i++) {
arr.push(i)
}
console.timeEnd("执行用时")
console.log('该我执行了')
运行结果如下:
我们可以看到,这段for循环执行了800多毫秒,这在计算机看来,运行时间是非常久的,并且要等for循环执行完,才能继续执行后续的代码,如果数据量再大一点,那么就必然会造成线程阻塞,甚至导致浏览器崩溃。
2. 使用worker的情况
子线程代码:
// 接收主线程信息
this.addEventListener('message', (e) => {
console.log('主线程传过来的数据:', e.data)
let arr = []
for (let i = 0; i < e.data; i++) {
arr.push(i)
}
postMessage(arr)
})
主线程代码
console.log('执行开始')
console.time("执行用时")
// 兼容性判断
if (!Worker) throw new Error('当前浏览器不支持多线程')
// 创建子线程
const childThread = new Worker('child.js')
// 向子线程发送数据
childThread.postMessage(10000000)
// 监听子线程信息
childThread.addEventListener('message', (e) => {
console.log('子线程回传的数据: ', e.data)
})
console.timeEnd("执行用时")
console.log('该我执行了');
运行结果如下:
我们可以看到,执行到 “该我执行了” 这段代码时,中间仅花了0.08毫秒左右,也就是说,这中间并不需要去等待worker的执行,等子线程执行完任务后,就会回传给主线程,主线程并不用担心子线程需要执行多久,结果是否失败,都不会影响到主线程的正常执行,这也就印证了web worker多线程的强大之处。
六、注意事项
- 主线程和子线程之间传递的消息是通过复制的方式,而不是共享,也就是说如果传递参数是一个对象,那么程序会使用JSON方式来编码/解码对象
- 主线程和子线程是不同上下文,不同作用域,不同环境,子线程不能操作主线程的dom和方法,比如:window、document、alert等对象
七、总结
以上就是关于web worker的使用与应用场景的简单模拟,以后在遇到类似业务场景时,我们就可以使用web worker去解决。希望本文能够对大家有所帮助,谢谢!
结合本文,出一道相信大家都听到过的面试题——如果后端一次性给你返回十万条数据,你会怎么处理?