Web Worker 的作用,就是为 JavaScript 创造多线程环境,允许主线程创建 Worker 线程,将一些任务分配给后者运行。在主线程运行的同时,Worker 线程在后台运行,两者互不干扰。等到 Worker 线程完成计算任务,再把结果返回给主线程。这样的好处是,一些计算密集型或高延迟的任务,被 Worker 线程负担了,主线程(通常负责 UI 交互)就会很流畅,不会被阻塞或拖慢。
Worker 线程一旦新建成功,就会始终运行,不会被主线程上的活动(比如用户点击按钮、提交表单)打断。这样有利于随时响应主线程的通信。但是,这也造成了 Worker 比较耗费资源,不应该过度使用,而且一旦使用完毕,就应该关闭。
Web Worker 有以下几个使用注意点。
(1)同源限制
分配给 Worker 线程运行的脚本文件,必须与主线程的脚本文件同源。
(2)DOM 限制
Worker 线程所在的全局对象,与主线程不一样,无法读取主线程所在网页的 DOM 对象,也无法使用document
、window
、parent
这些对象。但是,Worker 线程可以navigator
对象和location
对象。
(3)通信联系
Worker 线程和主线程不在同一个上下文环境,它们不能直接通信,必须通过消息完成。
(4)脚本限制
Worker 线程不能执行alert()
方法和confirm()
方法,但可以使用 XMLHttpRequest 对象发出 AJAX 请求。
(5)文件限制
Worker 线程无法读取本地文件,即不能打开本机的文件系统(file://
),它所加载的脚本,必须来自网络。
二、基本用法
2.1 主线程
主线程采用new
命令,调用Worker()
构造函数,新建一个 Worker 线程。
var worker = new Worker('work.js');
Worker()
构造函数的参数是一个脚本文件,该文件就是 Worker 线程所要执行的任务。由于 Worker 不能读取本地文件,所以这个脚本必须来自网络。如果下载没有成功(比如404错误),Worker 就会默默地失败。
然后,主线程调用worker.postMessage()
方法,向 Worker 发消息。
worker.postMessage('Hello World'); worker.postMessage({method: 'echo', args: ['Work']});
worker.postMessage()
方法的参数,就是主线程传给 Worker 的数据。它可以是各种数据类型,包括二进制数据。
接着,主线程通过worker.onmessage
指定监听函数,接收子线程发回来的消息。
worker.onmessage = function (event) { console.log('Received message ' + event.data); doSomething(); } function doSomething() { // 执行任务 worker.postMessage('Work done!'); }
上面代码中,事件对象的data
属性可以获取 Worker 发来的数据。
Worker 完成任务以后,主线程就可以把它关掉。
worker.terminate();
2.2 Worker 线程
Worker 线程内部需要有一个监听函数,监听message
事件。
self.addEventListener('message', function (e) { self.postMessage('You said: ' + e.data); }, false);
上面代码中,self
代表子线程自身,即子线程的全局对象。因此,等同于下面两种写法。
// 写法一 this.addEventListener('message', function (e) { this.postMessage('You said: ' + e.data); }, false); // 写法二 addEventListener('message', function (e) { postMessage('You said: ' + e.data); }, false);
除了使用self.addEventListener()
指定监听函数,也可以使用self.onmessage
指定。监听函数的参数是一个事件对象,它的data
属性包含主线程发来的数据。self.postMessage()
方法用来向主线程发送消息。
接下来我们通过一个简单的实例来了解下整个流程:
在我们的main.html页面中。点击一个按钮,我们将要计算一个0-10000次的累加运算。该运算我们希望把它放到worker线程中去,而不阻塞我们的js代码。当worker线程运算结束以后把结果返回给主线程。然后我们在main.html页面中把结果显示出来。
-----------------------------------------------------------------------------------------------------------
main.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<style>
#res{color:red;}
</style>
</head>
<body>
<div id="box">最后通过子进程计算的是:<span id="res"></span></div>
<button id="btn" >点击</button>
</body>
</html>
<script>
window.οnlοad=function(){
var box=document.getElementById("box");
var btn=document.getElementById("btn");
var res=document.getElementById("res");
var mainWork=new Worker("http://localhost:8989/webWorker.js")
var num=0;
btn.οnclick=function(){
mainWork.postMessage({num:0})
mainWork.onmessage =function(event){
console.log("主线程接受来自子线程的数据:",event.data)
res.innerHTML=event.data.num;
mainWork.terminate()
}
}
</script>
上面的代码中我们新建一个num变量,初始值是0,我们需要把它通过主线程发送至子线程。然后通过onme'ssage事件接收子线程的数据,最后显示在res的页面元素中。真个过程结束以后我们通过mainWork.terminate()结束子线程,避免内存的消耗
注意:1 worker对象是html原生支持的对象。它不像sock.io需要我们引入sock.io的script才能使用。直接new Worker()实例即可
2worker不能在本地执行。所以我们的var mainWork=new Worker("http://localhost:8989/webWorker.js")引入的是一个本地服务器地址的js文件,同样,对于main.html元素而言。也必须在服务器环境中。所以main.html必须通过localost:8989/main.html运行才能正常运行。如果在本地路径file://e/work/main.html直接通过浏览器运行会报错
----------------------------------------------------------------------------------------------------------------
本地服务器环境搭建
var express=require("express");
var app=express();
app.use(express.static("static"));
app.get("/",function(req,res){
res.sendFile(__dirname+"/work.html")
}).listen(8989)
通过express搭建服务器环境,我们把worker.js文件放在了静态资源的目录static文件夹下,这样通过http://localhost:8989/webWorker.js就能直接访问到它
------------------------------------------------------------------------------------------------------
worker.js文件
self.addEventListener("message",function(e){
self.console.log("子进程获取到的数据是:",e)
var data=e.data.num;
for(var i=0;i<10000;i++){
data+=i
}
console.log("子进程的data:",data)
self.postMessage({num:data})
})
子进程中监听message事件,通过回调函数的event对象即可得到从主线程传递过来的数据,该数据挂载到了e.data上,我们通过e.data.num就能得到主进程传递过来的num:0;子进程再通过 postMessage()把计算好的值传递给主进程。这样就实现了整个主进程与子进程的通讯过程