一、为啥js是单线程
一个简单的原因就是,js在设计之初只是进行一些简单的表单校验,这完全不需要多线程,单线程完全可以胜任这项工作。即便后来前端发展迅速,承载的能力越来越多,也没有发展到非多线程不可的程度。
还有一个主要的原因,如果js是多线程的,在运行时多个线程同时对DOM元素进行操作,那具体以哪个线程为主?由此可见,线程的调度问题是一个比较复杂的问题。
HTML5新的标准中允许使用new Worker的方式来开启一个新的线程,去运行一段单独的js文件脚本。
但是在这个新线程中严格的要求了可以使用的功能,比如说他只能使用ECMAScript, 不能访问DOM和BOM。这也就限制死了多个线程同时操作DOM元素的可能。
二,关于web Worker实现多线程
web Worker可以分为以下两种:
1.专用线程(Dedicated Web Worker),仅能被创建它的脚本所使用(一个专用线程对应一个主线程),一般默认情况下的web worker都是指专用线程。
2.共享线程(Shared Web Worker),能够在不同的脚本中使用(一个共享线程对应多个主线程)。
主要用途
也是用于将耗时的数据处理操作从主线中剥离出来,减轻主线程的压力。
懒加载|、文本分析、流媒体数据处理、canvas 图形绘制、图像处理等等都可以
限制
初步介绍的时候也提及过,它是无法访问 DOM 节点的
虽然Web Worker 的运行不会影响主线程,但与主线程交互时仍受到主线程单线程的瓶颈制约。如果 Worker 线程频繁与主线程进行交互,主线程由于需要处理交互,仍可能使页面发生阻塞
运行在另一个上下文中,无法使用Window对象
有同源限制。共享线程可以被多个浏览上下文调用,但所有这些浏览上下文必须同源。
使用
不论共享线程还是专用线程它的构造方法都在window之内,使用前可以对浏览器的支持进行一个处理。
if (window.Worker){}
if (window.SharedWorker) {}
1.专用线程的创建
var worker = new Worker('A.js', { name: 'B'})
第一个参数 必须。脚本位置
第二个参数 可选,指定 type、credentials、name 三个属性。
2.共享线程的创建
var sharedWorker = new SharedWorker('A.js')
第一个参数 必须。脚本位置
第二个参数 可选,指定 type、credentials、name 三个属性。
【 Web Worker 有同源限制,所以在本地调试的时候也需要通过启动本地服务器的方式访问,使用 file:// 协议直接打开的话将会抛出异常】
数据传递
Worker 线程和主线程都通过postMessage()方法发送消息,通过onmessage事件接收消息。
在这个过程中数据并不是被共享的,而是被复制的。
Error和Function对象不能被结构化克隆算法复制,如果尝试这么做的话会导致抛出DATA_CLONE_ERR的异常。
另外,postMessage()一次只能发送一个对象, 多个参数可以将参数包装为数组或对象再进行传递。
主线程代码:
let worker=new Worker('worker.js');
worker.postMessage([10,24]);
worker.onmessage=function (e){ console.log(e.data)}
worker线程代码
onmessage = function (e) {
console.log(e);
if (e.data.length > 1) {
setTimeout(function (){ postMessage(e.data[1] - e.data[0]) } ,1000)
}
}
在 Worker 线程中,self 和 this 都代表子线程的全局对象。
对于监听 message 事件,以下的四种写法是等同的:
self.addEventListener('message', function (e) {})
this.addEventListener('message', function (e) {})
addEventListener('message', function (e) {})
onmessage = function (e) {}
主线程通过MessagePort 来访问worker线程。专用线程的 port 会在线程创建时自动设置,并且不会暴露出来。共享线程在传递消息之前,端口必须处于打开状态。start() 方法是与 addEventListener 配套使用。选择 onmessage 进行事件监听,那么将隐含调用 start() 方法。
// 主线程var sharedWorker = new SharedWorker('shared-worker.js')
sharedWorker.port.onmessage = function(e) { // 业务逻辑 }
var sharedWorker = new SharedWorker('shared-worker.js')
sharedWorker.port.addEventListener('message', function(e) { // 业务逻辑 }, false)
sharedWorker.port.start() // 需要显式打开
在传递消息时,postMessage() 方法和 onmessage 事件必须通过端口对象调用。另外,在 Worker 线程中,需要使用 onconnect 事件监听端口的变化,并使用端口的消息处理函数进行响应。
// 主线程sharedWorker.port.postMessage([10, 24])sharedWorker.port.onmessage = function (e) { console.log(e.data)}// Worker 线程onconnect = function (e) { let port = e.ports[0] port.onmessage = function (e) { if (e.data.length > 1) { port.postMessage(e.data[1] - e.data[0]) } }}
关闭 Worker
主线程中使用terminate()方法;
Worker 线程中使用close()方法。
推荐的用法是在worker线程中关闭,防止意外关闭正在运行的 Worker 线程,Worker 线程一旦关闭之后的Worker 将不再响应。
错误处理
可以在线程中设置 onerror 和 onmessageerror 的回调函数对错误进行处理。
// 主线程
worker.onerror = function () {}
// 主线程使用专用线程
worker.onmessageerror = function () {}
// 主线程使用共享线程
worker.port.onmessageerror = function () {}
// worker 线程
onerror = function () {}
加载外部脚本
使用importScripts() 方法
importScripts('A.js')
importScripts(B.js')
importScripts(A.js', 'B.js')
子线程
worker可以生成子worker,但是必须同源,且子worker的中的中的 URI 要相对于父worker的地址进行解析。
嵌入式Worker
可以通过Bolb()将页面中的Worker代码进行解析。
//这段代码不会被 JS 引擎直接解析,因为类型是 'javascript/worker'
// 在这里写 Worker 线程的逻辑
var workerScript = document.querySelector('#worker').textContent
var blob = new Blob(workerScript, {type: "text/javascript"})
var worker = new Worker(window.URL.createObjectURL(blob))
关于 postMessage
Web Worker 中,Worker 线程和主线程之间使用结构化克隆算法(The structured clone algorithm)进行数据通信。
这是一种同过递归输入对象构建克隆的算法,通过保存之前访问过的映射,避免无线遍历循环。
worker提出了Transferable Objects 的概念,防止因为数据传输量过大的时 所造成的性能问题。可以在数据量大的时候,将主线程中的数据直接移交给Worker线程。这种移交是彻底的,一旦数据成功转移,主线程将不能访问该数据。这个过程还是通过postMessage进行传递的。
postMessage(message, transferList)
let aBuffer = new ArrayBuffer(1)
worker.postMessage({ data: aBuffer }, [aBuffer])
上下文
Worker 工作在一个 WorkerGlobalDataScope 的上下文中。
每一个WorkerGlobalDataScope对象都有不同的event loop。这个event loop没有关联浏览器上下文(browsing context),它的任务队列也只有事件、回调和联网的活动。
每一个WorkerGlobalDataScope都有一个closing标志,当这个标志设为true时,任务队列将丢弃之后试图加入任务队列的任务,队列中已经存在的任务不受影响。同时,定时器将停止工作,所有挂起的后台任务将会被删除。
Worker 中可以使用的函数和类
由于 Worker 工作的上下文不同于普通的浏览器上下文,因此不能访问 window 以及 window 相关的 API,也不能直接操作 DOM。Worker 中提供了WorkerNavigator和WorkerLocation接口,它们分别是 window 中Navigator和Location的子集。除此之外,Worker 还提供了涉及时间、存储、网络、绘图等多个种类的接口,以下列举了其中的一部分,更多的接口可以参考MDN 文档。
时间相关:clearInterval(),clearTimeout(),setInterval(),setTimeout
Worker 相关:importScripts(),close(),postMessage()
存储相关:Cache,IndexedDB
网络相关:Fetch,WebSocket,XMLHttpRequest