web worker和service worker

3,981 阅读5分钟

webwork解决js如果执行时间过长就会阻塞页面问题

众所周知,JS是运行在单线程环境中的,也就是说无法同时运行多个脚本。假设用户点击了一个按钮,出发了一段用于计算的js代码,那么在这段代码执行完成之前,是无法响应用户其他操作的。但是,如果将这段代码交给webwok去运行的话,情况就会不一样了:浏览器会在后台启动一个单独的worker线程来专门负责这段代码的运行,因此,页面在这段js代码运行期间依然可以响应用户的其他操作

web worker简介

web worker是HTML5标准的一部分,这一规范定义了一套API,它允许一段js程序运行在主线程另一线程中。

web worker 规范定义了两类工作线程

dedicated worker(专用线程):一个页面使用

shared worker(共享线程):可以多个页面共享

web worker会为js带来了什么

强大的计算能力

可以加载一个js进行大量的复杂计算而不挂起主进程,并通过postMessage,onmessage进行通信,解决了大量计算对UI渲染的阻塞问题

特点

  1. 同源限制 分配给worker线程运行的脚本文件,必须与主线程的脚本文件同源

  2. DOM限制 worker线程所在的全局对象,与主线程不一样,无法读取主线程所在网页的DOM对象,也无法使用document,window,parent这些对象。但是,worker线程有navigator对象和location对象

  3. 通信联系 worker线程和主线程不在同一个上下文环境,他们不能直接通信,必须通过消息完成

4.脚本限制 worker线程不能执行alert()方法和confirm()方法,但是可以使用XMLHttpRequest对象发出AJAX请求

  1. 文件限制 work线程无法读取本地文件,既不可以打开本季的文件系统,所加载的脚本,必须来自网络

应用场景

  1. 数学运算

webworker最简单的应用就是用来做后台计算,对CPU密集型的场景再合适不过了

  1. 图像处理

通过使用从中获取的数据,可以把图像分割成几个不同的区域并把他们推送给并行的不同的worker来做计算,对图像进行像素级的处理,再把处理完成的图像数据返回给主页面

  1. 大数据的处理

目前mvvm框架越来越普及,基于数据驱动的开发模式也越来越流行,未来大数据的处理也可能转向走到前台,这时,将大数据的处理交给在web worker也是上上之策

webworker的使用方法

创建worker

只需要调用worker()构造函数并传入一个要在worker线程内运行的急哦啊笨的URL,既可创建一个新的worker。

var myWorker = new Worker('my-task.js');

//my-task.js中的代码

var i = 0;
function timedCount(){
    i = i+1;
    postMessage(i);
    setTimeout(timedCount, 1000);
}
timedCount();

另外通过URL.creatObjectURL()创建URL对象,可以实现内嵌的webworker

var myTask = `
    var i = 0;
    function timedCount(){
      if(i<=3){
          i = i+1;
          postMessage(i);
          setTimeout(timedCount, 1000);
      }
    }
    timedCount();
`;

var blob = new Blob([myTask]);
var myWorker = new Worker(window.URL.createObjectURL(blob));

在这样,就可以结合NEJ,Webpack进行模块管理,打包了

注意:传入worker构造函数的参数必须遵循同源策略。worker线程的创建的是与不的,主线程代码不会阻塞在这里等待worker线程去架子啊、执行指定的脚本文件,而是立即向下继续执行后面代码

worker线程数据通讯方式

worker与其主页面之间的通信是通过onmessage事件和postMessage()方法实现的。

在主页面与Worker之间传递的数据是通过拷贝,而不是共享来完成的。传递给workrt饿对象需要讲过序列化,接下来在另一端还需要反序列化。叶脉浓郁worker不会共享同一个实例,最终的结果就是在每次通讯结束时生成了数据的一个副本。

也就是说,worker与其主页面之间只能单纯的传递数据,不能传递复杂的引用类型:如通过构造函数创建的对象等。并且,传递的数据也是经过拷贝生成的一个副本,在一端对数据进行修改不会影响另一端。

var myTask = `
    onmessage = function (e) {
        var data = e.data;
        data.push('hello');
        console.log('worker:', data); // worker: [1, 2, 3, "hello"]
        postMessage(data);
    };
`;

var blob = new Blob([myTask]);
var myWorker = new Worker(window.URL.createObjectURL(blob));

myWorker.onmessage = function (e) {
    var data = e.data;
    console.log('page:', data); // page: [1, 2, 3, "hello"]
    console.log('arr:', arr); // arr: [1, 2, 3]
};

var arr = [1,2,3];
myWorker.postMessage(arr);

通过可转让对象来传递数据

通过可转让对象来传递数据性能更高,通过可转让对象将数据在主页面和worker之间进行来回穿梭。可转让对象从一个上下文转移到另一个上下文而不会经过任何拷贝操作。这意味着当传递大数据时会获得极大的性能提升。

和按照引用传递不同,一旦对象转让,那么它在原来上下文的那个版本将不复存在。该对象的所有权被转让到新的上下文内。

例如,当你将一个ArrayBuffer对象从主应用转让到worker中,原始的ArrayBuffr被清空无法使用。它包含的内容会(完整无差)传递给worker上下文。

var uInt8Array = new Uint8Array(1024*1024*32); // 32MB
for (var i = 0; i < uInt8Array .length; ++i) {
  uInt8Array[i] = i;
}

console.log(uInt8Array.length); // 传递前长度:33554432

var myTask = `
    onmessage = function (e) {
        var data = e.data;
        console.log('worker:', data);
    };
`;

var blob = new Blob([myTask]);
var myWorker = new Worker(window.URL.createObjectURL(blob));
myWorker.postMessage(uInt8Array.buffer, [uInt8Array.buffer]);

console.log(uInt8Array.length); // 传递后长度:0

importScripts()

Worker 线程能够访问一个全局函数imprtScripts()来引入脚本,该函数接受0个或者多个URI作为参数。 浏览器加载并运行每一个列出的脚本,每个脚本中的全局对象都能够被 worker 使用。如果脚本无法加载,将抛出 NETWORK_ERROR 异常,接下来的代码也无法执行。而之前执行的代码(包括使用 window.setTimeout() 异步执行的代码)依然能够运行。importScripts() 之后的函数声明依然会被保留,因为它们始终会在其他代码之前运行。 注意:脚本的下载顺序不固定,但执行时会按照传入 importScripts() 中的文件名顺序进行。这个过程是同步完成的;直到所有脚本都下载并运行完毕, importScripts() 才会返回。

worker上下文

Worker执行的上下文,与主页面执行时的上下文并不相同,最顶层的对象并不是window,而是个一个叫做WorkerGlobalScope的东东,所以无法访问window、以及与window相关的DOM API,但是可以与setTimeout、setInterval等协作。 WorkerGlobalScope作用域下的常用属性、方法如下:

  1. self 我们可以使用 WorkerGlobalScope 的 self 属性来或者这个对象本身的引用

  2. location location 属性返回当线程被创建出来的时候与之关联的 WorkerLocation 对象,它表示用于初始化这个工作线程的脚步资源的绝对 URL,即使页面被多次重定向后,这个 URL 资源位置也不会改变。

  3. close 关闭当前线程

  4. importScript 我们可以通过importScripts()方法通过url在worker中加载库函数

  5. XMLHttpRequest 有了它,才能发出Ajax请求

  6. setTimeout/setInterval以及addEventListener/postMessage

终止:在主页页面上调用terminate()方法,可以立即杀死worker线程,不会留下任何机会让它完成自己的操作或清理工作。另外worker也可以调用自己的close()方法来关闭自己

    // 主页面调用
    myWorker.terminate();

    // Worker 线程调用
    self.close();

处理错误

当worker出现运行时错误时,它的onerror事件处理函数会被调用。它会收到一个实现了errorEvent接口名为error的事件。

该事件不会冒泡,并且可以被取消

为了防止触发默认动作,worker可以调用错误事件的preventDefault()方法

错误事件有三个使用的属性:filename-发生错误的脚本文件名;lineno-出现错误的行号;以及message-可读性良好的错误信息

var myTask = `
    onmessage = function (e) {
        var data = e.data;
        console.log('worker:', data);
    };

    // 使用未声明的变量
    arr.push('error');
`;

var blob = new Blob([myTask]);
var myWorker = new Worker(window.URL.createObjectURL(blob));
myWorker.onerror = function onError(e) {
    // ERROR: Line 8 in blob:http://www.cnblogs.com/490a7c32-7386-4d6e-a82b-1ca0b1bf2469: Uncaught ReferenceError: arr is not defined
    console.log(['ERROR: Line ', e.lineno, ' in ', e.filename, ': ', e.message].join(''));
}

Service Worker

是什么

一个服务器与浏览器之间的中间人角色,如果网站中注册了service worker那么它可以拦截当前网站所有的请求,进行判断(需要编写相应的判断程序),如果需要向服务器发起请求的就转给服务器,如果可以直接使用缓存的就直接返回缓存不再转给服务器。从而大大提高浏览体验。

细碎的描述

  • 基于web Worker(一个独立于JavaScript主线程的独立线程,在里面执行需要消耗大量资源的操作不会阻塞主线程)
  • 本质上充当服务器与浏览器之间的代理服务器(可以拦截全站的请求,并作出相应的动作->由开发者指定的动作
  • 在web worker的基础上增加了离线缓存的能力
  • 创建有效的离线体验(将一些不常更新的内容缓存在浏览器,提高访问体验)
  • 由事件驱动的,具有生命周期
  • 可以访问catche和indexDB
  • 支持推送
  • 并且可以让开发者自己控制管理缓存的内容以及版本

注意事项

  1. service worker运行在worker上下文-----不可以访问DOM
  2. 完全异步,同步API(如XHR和localStorage)不能再service work中使用
  3. 处于安全考虑,service worker只能为https承载
  4. 在Firefox浏览器的用户隐私模式,service work不可用
  5. 其生命周期与页面无关(关联页面未关闭时,它也可以退出,没有关联页面,它也可以启动)