Web Workers基本知识

1,112 阅读7分钟

Web Workers的出现

1、我们知道,JavaScript语言的一大特点就是单线程,也就是说,同一个时间只能做一件事

2、当JS在页面中运行长耗时同步任务的时候就会导致页面假死影响用户体验,从而需要设置把任务放在任务队列中,执行任务队列中的任务也并非多线程进行的

3、然而现在HTML5提供了我们前端开发这样的能力 - Web Workers API,

Web Workers 是什么

  • Web Workers 使得一个Web应用程序可以在与主执行线程分离的后台线程中运行一个脚本操作
    • 这样做的好处是可以在一个单独的线程中执行费时的处理任务
    • 从而允许主(通常是UI)线程运行而不被阻塞。

当在 HTML 页面中执行脚本时,页面的状态是不可响应的,直到脚本已完成。

web worker 是运行在后台的 JavaScript,独立于其他脚本,不会影响页面的性能。您可以继续做任何愿意做的事情:点击、选取内容等等,而此时 web worker 在后台运行。一般来说Javascript和UI页面会共用一个线程,在HTML页面中执行js脚本时,页面的状态是不可响应的,直到脚本已完成。而这段代码可以交给Web Worker在后台运行,那么页面在Javascript运行期间依然可以响应用户操作。后台会启动一个worker线程来执行这段代码,用户可以创建多个worker线程。

javascript单线程产生的原因

JavaScript的单线程,与它的用途有关。作为浏览器脚本语言,JavaScript的主要用途是与用户互动,以及操作DOM。这决定了它只能是单线程,否则会带来很复杂的同步问题。比如,假定JavaScript同时有两个线程,一个线程在某个DOM节点上添加内容,另一个线程删除了这个节点,这时浏览器应该以哪个线程为准?为了利用多核CPU的计算能力,HTML5提出Web Worker标准,允许JavaScript脚本创建多个线程,但是子线程完全受主线程控制,且不得操作DOM。所以,这个新标准并没有改变JavaScript单线程的本质。

任务队列

单线程就意味着,所有任务需要排队,前一个任务结束,才会执行后一个任务。如果前一个任务耗时很长,后一个任务就不得不一直等着。所有任务可以分成两种,一种是同步任务(synchronous),另一种是异步任务(asynchronous)。同步任务指的是,在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务;异步任务指的是,不进入主线程、而进入"任务队列"(task queue)的任务,只有"任务队列"通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行。

任务队列执行特点

1、所有同步任务都在主线程上执行,形成一个执行栈(execution context stack)。

2、 主线程之外,还存在一个"任务队列"(task queue)。只要异步任务有了运行结果,就在"任务队列"之中放置一个事件。

3、 一旦"执行栈"中的所有同步任务执行完毕,系统就会读取"任务队列",看看里面有哪些事件。那些对应的异步任务,于是结束等待状态,进入执行栈,开始执行。

4、主线程不断重复上面的第三步。

有两种 Web Worker

专用线程dedicated web worker,以及共享线程shared web worker。 Dedicated web worker随当前页面的关闭而结束;这意味着Dedicated web worker只能被创建它的页面访问。与之相对应的Shared web worker可以被多个页面访问。在Javascript代码中,“Work”类型代表Dedicated web worker,而“SharedWorker”类型代表Shared web worker。

  • new Worker()对象代表Dedicated Web Worker

Web Workers 的作用

  • 1、它的作用就是给JS创造多线程运行环境、允许主线程创建worker线程,分配任务给后者,主线程运行的同时worker线程也在运行,相互不干扰,在worker线程运行结束后把结果返回给主线程
    • 这样做的好处是主线程可以把计算密集型或高延迟的任务交给worker线程执行,这样主线程就会变得轻松,不会被阻塞或拖慢。这并不意味着JS语言本身支持了多线程能力,而是浏览器作为宿主环境提供了JS一个多线程运行的环境。
    • 不过因为worker一旦新建,就会一直运行,不会被主线程的活动打断,这样有利于随时响应主线程的通性,但是也会造成资源的浪费,所以不应过度使用,用完注意关闭

使用Web Workers的限制

  • 1、同源限制 worker线程执行的脚本文件必须和主线程的脚本文件同源,这是当然的了,总不能允许worker线程到别人电脑上到处读文件吧
  • 2、文件限制 为了安全,worker线程无法读取本地文件,它所加载的脚本必须来自网络,且需要与主线程的脚本同源
  • 3、DOM操作限制 worker线程在与主线程的window不同的另一个全局上下文中运行,其中无法读取主线程所在网页的DOM对象,也不能获取 document、 window等对象,但是可以获取 navigator、 location(只读)、XMLHttpRequest、 setTimeout族等浏览器API。
  • 4、通信限制 worker线程与主线程不在同一个上下文,不能直接通信,需要通过 postMessage方法来通信。
  • 5、脚本限制 worker线程不能执行 alert、 confirm,但可以使用 XMLHttpRequest 对象发出ajax请求。

创建web Worker

  • 创建一个新的 worker 十分简单。你所要做的就是调用 Worker() 构造函数,指定一个要在 worker 线程内运行的脚本的 URI

  • var myWorker = new Worker("worker_demo.js");
    
  • 可以将worker的onmessage属性设置成一个特定的事件处理函数,当 web worker 传递消息时,会执行事件监听器中的代码。event.data 中存有来自 worker 的数据

  • myWorker.onmessage = function (event) {
      console.log("event.data");
    };
    
    

WebWorker之常用API

  • 1、postMessage(data)

    子线程与主线程之间互相通信使用方法,传递的data为任意值

var w  = new Worker('index.js')
//worker.postMessage传递给子线程数据,对象
w.psotMessage({name:'h'});
//子线程中也可以使用postMessage,如传递字符串
postMessage(‘test’);

  • 2、terminate()

    主线程中终止worker,此后无法再利用其进行消息传递。注意:一旦terminate后,无法重新启用,只能另外创建

//worker = new Worker('url');
 worker.terminate();

父子线程相互操作

父线程代码

<!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>
</head>
<body onload='init()'>
    <div id='result'> </div>
    <script>
        //父线程
        function init(){
            var worker = new Worker('worker.js')
            //每隔100毫秒,向子线程传递{name:'qqqiu'} 信息
            setInterval(function (){
                worker.postMessage({name:'qqqiu'})
            },100)
            //当主线程worker收到来自子线程的消息后,触发message事件
            worker.onmessage = function(event){
                console.log(event.data)
                document.getElementById('result').innerHTML +=event.data +'<br/>'
                //主线程使用terminate方法中断与子线程通信,在浏览器中只能显示一次event.data
                worker.terminate()
            }
        }
    </script>
</body>
</html>

子线程代码

worker.js

//子线程代码
//当主线程发来消息后,触发改message事件
onmessage = function(event){
    //向主线程发送event.data.name信息
    postMessage(event.data.name)
}

注意

在Chrome中运行Web Workers多线程程序,出现以下错误提示:

Uncaught DOMException: Failed to construct ‘Worker’

原因在于文件限制 为了安全,worker线程无法读取本地文件,解决办法:

使用别的浏览器重新进行尝试,比如IE浏览器。 将程序部署在服务器下,

message事件

  • 当有消息发送时,触发该事件。且,消息发送是双向的,消息内容可通过data来获取。message使用。
error 出错处理。且错误消息可以e.message来获取。
worker.onerror = function(e){
  // 打印出错消息
  console.log(e.message);
 // 中断子线程的联系
 worker.terminate();
}

另:worker线程从上到下同步运行它的代码,然后进入异步阶段来对事件及计时器响应,如果worker注册了message事件处理程序,只要其有可能触发,worker就一直在内存中,不会退出,所以通信完毕后得手动在主线程中terminate或者子线程中close掉,但如果worker没有监听消息,那么当所有任务执行完毕(包括计数器)后,他就会退出。