当我们开始学习 javascript 的时候,我们就知道 js 其实是单线程的,所以当我们在浏览器中运行某些耗时算法或者阻塞线程的代码时,浏览器就会出现卡顿的现象
然而浏览器的单个页面却拥有多个线程,比如渲染界面线程、浏览器事件触发线程、http 请求线程、事件轮询处理线程等
如果我们能够将一部分代码放在一个新的线程中执行,比如 http 请求的方法、需要大量计算耗时较多的方法,既能够保证页面对用户及时响应,又不会阻塞页面
这在以前是不可能的,但是现在,H5 为我们提供了一个方法 —— webWorker
什么是 webWorker
webWorker 是浏览器为我们提供的一个可以再浏览器后台开启一个新的线程的 API,使得运行在浏览器中的 js 有了多线程的能力。但是这并不意味这 js 本身就支持多线程
webWorker 有两种类型,一种是只能在当前页面使用的 webworker,另一种是可以再多个页面之间共享线程的 webWorker,前者随着当前页面关闭而关闭,而后者在同域的前提下,可以被多个页面访问
webWorker 的创建与使用
// webWorker 是在主线程中通过传入一个 js 文件的路径来实现的,它返回一个 webWorker 的实例对象,该对象是主线程与该线程通信的桥梁
let worker = new Woker ('webWorker.js')
webWorker 在主线程和子线程之间实现通信的方法有两个:
// 监听一个线程向另一个发送的消息并执行指定方法
// 回调方法接受一个 event 参数,event.data 为接受到的数据
onmessage = (event) => {}
// 一个线程向另一个线程发送消息
postMessage(data)
实际使用时可以像下面这样使用:
/**
* 主线程
*/
let worker = new Worker ('worker.js')
worker.onmessage = (e) => {
console.log(e.data) // I post a message to main thread
}
worker.postMessage('main thread got a message')
/**
* 子线程 worker.js
*/
onmessage = (e) => {
console.log(e.data) // main thread got a message
}
postMessage('I post a message to main thread')
终止 webWorker
// 在主线程中终止
worker.terminate()
// 在子线程中终止自身
self.close()
错误监听
当子线程发生错误时,可以在主线程中监听到该错误
worker.addEventListener('error', (e) => {
console.error(e.filename) // 导致错误的 worker 脚本名称
console.error(e.message) // 错误信息
console.error(e.lineno) // 错误行号
})
引入其他脚本
// 引入一个脚本
importScripts('xxx.js');
// 引入多个脚本
importScripts('aaa.js', 'bbb.js', 'ccc.js');
importScripts 会按顺序加载每一个脚本,当有任何失败或者错误时,会抛出 SYNTAX_ERR 异常
共享线程的使用
主线程中创建一个共享线程
let shareWorker = new SharedWorker ('shareWorker.js')
shareWorker.port.start()
shareWorker.port.postMessage('...')
shareWorker.port.onmessage = (e) => {...}
子线程 shareWorker.js
onconnect = (e) => {
let port = e.ports[0]
port.addEventListener('message', (e) => {
console.log(e.data[0])
port.postMessage(...);
});
port.start();
}
postMseeage 方法
postMseeage 传递数据的过程其实是一个值拷贝的过程,会现将数据 JSON.stringify 之后再 JSON.parse
postMseeage 也可以传送二进制数据,但是当数据过大时,由于值拷贝,浏览器会再生成一个该文件的拷贝,这样可能会引起浏览器性能的问题
所以当传输较大数据时,可以直接将数据转移给另一个线程,而不进行值拷贝,只是这样会导致原线程无法再使用这些数据,也能够防止多个线程同时修改的情况发生,这叫做零拷贝
// 指定传输的所有数据都是零拷贝
let data = new ArrayBuffer(64)
worker.postMessage(data, [data])
// 指定数据中的某个属性零拷贝
let obj = {a: 1, b: 2, c: 3}
worker.postMessage(obj, [obj.a, obj.c])
扫码关注前端周记公众号需要注意的是,通过 webWorker 创建的线程的运行环境中没有全局对象 window,也无法访问 DOM / BOM 对象,所以他只能用来执行纯粹的 javascript 计算
当然,他也可以获取到部分浏览器提供的 API,如:
XMLHttpRequest
navigator
location (read only)
setTimeout(), clearTimeout(), setInterval(), clearInterval()
Promise 等等
还有哪些,小伙伴们可以自己去尝试发掘(可以将 self 打印出来看看)
网友评论