关于js的单多线程问题

一、为啥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代码进行解析。



关于 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

你可能感兴趣的:(关于js的单多线程问题)