JavaScript是多线程还是单线程?
JavaScript语言的一大特点就是单线程。作为浏览器脚本语言,JavaScript的主要用途是与用户互动,以及操作DOM。这决定了它只能是单线程,否则会带来很复杂的同步问题。比如,假定JavaScript同时有两个线程,一个线程在某个DOM节点上添加内容,另一个线程删除了这个节点,这时浏览器应该以哪个线程为准?
随着电脑计算能力的增强,尤其是多核 CPU 的出现,单线程带来很大的不便,无法充分发挥计算机的计算能力。HTML5提出 Web Worker
标准,允许JavaScript脚本创建多个线程,但是 子线程完全受主线程控制,且不得操作DOM。所以,这个新标准 并没有改变JavaScript单线程的本质
HTML5 - Web Worker
Web Worker 概念
JS提供了一个 Worker
的类,当我们使用这个类的时候,它就会向 浏览器 申请一个新的线程。这个线程就用来 单独执行一个js文件。等到 Worker 线程完成计算任务,再把结果返回给主线程。这样的好处是,一些计算密集型或高延迟的任务,被 Worker 线程负担了,主线程(通常负责 UI 交互)就会很流畅,不会被阻塞或拖慢。
Worker 线程一旦新建成功,就会始终运行,不会被主线程上的活动(比如用户点击按钮、提交表单)打断。这样有利于随时响应主线程的通信。但是,这也造成了 Worker 比较耗费资源,不应该过度使用,而且一旦使用完毕,就应该关闭。
Web Worker 使用注意点:
-
同源限制
分配给 Worker 线程运行的脚本文件,必须与主线程的脚本文件同源。 -
DOM 限制
Worker 线程所在的全局对象,与主线程不一样,无法读取主线程所在网页的 DOM 对象,也无法使用document
、window
、parent
这些对象。但是,Worker 线程可以使用navigator
对象和location
对象。 -
通信联系
Worker 线程和主线程不在同一个上下文环境,它们不能直接通信,必须通过 消息 完成。 -
脚本限制
Worker 线程不能执行alert()
方法和confirm()
方法,但可以使用 XMLHttpRequest 对象发出 AJAX 请求。 -
文件限制
Worker 线程无法读取本地文件,即不能打开本机的文件系统(file://)
,它所加载的脚本,必须来自网络。
Web Worker 使用
- 主线程新建worker线程
var worker = new Worker("work.js");
- 主线程向worker线程 发消息
worker.postMessage(参数); //参数就是主线程传给 Worker 的数据,可以是文本、对象等等
- 主线程 接收 来自worker线程的消息
worker.onmessage = function (event) {
console.log('Received message ' + event.data);
}
- 主线程 关闭 worker线程
worker.terminate();
- worker线程 接收 来自主线程的消息
Worker 线程内部需要有一个监听函数,监听message
事件。
其中,self代表子线程自身,即子线程的全局对象
this.addEventListener('message', function (e) {
self.postMessage('You said: ' + e.data);
}, false)
- worker线程向主线程 发送 消息
self.postMessage('this is worker');
- worker线程 关闭 自身
self.close();
- 错误处理
// 主线程
Worker.onerror:指定 error 事件的监听函数。
//worker线程
self.onmessageerror:指定 messageerror 事件的监听函数。发送的数据无法序列化成字符串时,会触发这个事件。
Web Worker 数据通信
主线程给worker线程发送的数据是 文本、对象 的话是拷贝的形式发送。即是传值而不是传址,Worker 对通信内容的修改,不会影响到主线程。事实上,浏览器内部的运行机制是,先将通信内容串行化,然后把串行化后的字符串发给 Worker,后者再将它还原。
主线程给worker线程发送的数据是 二进制数据,但是,拷贝方式发送二进制数据,会造成性能问题。比如,主线程向 Worker 发送一个 500MB 文件,默认情况下浏览器会生成一个原文件的拷贝。为了解决这个问题,JavaScript 允许主线程把二进制数据直接转移给子线程,但是一旦转移,主线程就无法再使用这些二进制数据了,这是为了防止出现多个线程同时修改数据的麻烦局面。这种转移数据的方法,叫做Transferable Objects。这使得主线程可以快速把数据交给 Worker,对于影像处理、声音处理、3D 运算等就非常方便了,不会产生性能负担。