web worker

  1. 介绍

    web worker HTML5提供得,运行在后台的 JavaScript,独立于其他脚本,不会影响页面的性能。您可以继续做任何愿意做的事情:点击、选取内容等等,而此时 web worker 在后台运行。

  2. 作用

    给JS创造多线程运行环境,允许主线程创建worker线程,分配任务给后者,主线程运行的同时worker线程也在运行,相互不干扰,在worker线程运行结束后把结果返回给主线程。

    这样做的好处是主线程可以把计算密集型或高延迟的任务交给worker线程执行,这样主线程就会变得轻松,不会被阻塞或拖慢。

    这并不意味着JS语言本身支持了多线程能力,而是浏览器作为宿主环境提供了JS一个多线程运行的环境。

    不过因为worker一旦新建,就会一直运行,不会被主线程的活动打断,这样有利于随时响应主线程的通性,但是也会造成资源的浪费,所以不应过度使用,用完注意关闭。

  3. 兼容性

    Browser IE Edge FireFox Chrome Safari
    version 10+ 12+ 3.5+ 4+ 4+
  4. 使用

    • 注意事项:

      • 同源限制:分配给 Worker 线程运行的脚本文件,必须与主线程的脚本文件同源。
      • DOM操作限制:Worker 线程所在的全局对象,与主线程不一样,无法读取主线程所在网页的 DOM 对象。也不能获取 documentwindow等对象,但是可以获取 navigatorlocation(只读)XMLHttpRequestsetTimeout族等浏览器API。
      • 通信限制:Worker 线程和主线程不在同一个上下文环境,它们不能直接通信,必须通过消息完成。
      • 脚本限制:Worker 线程不能执行alert()方法和confirm()方法,但可以使用 XMLHttpRequest 对象发出 AJAX 请求。
      • 文件限制:Worker 线程无法读取本地文件,即不能打开本机的文件系统(file://),它所加载的脚本,必须来自网络。
    • 例子:

      • 主线程

        • 创建 Worker 线程:
        // 两个参数 
        var myWorker = new Worker(jsUrl, options);
        

        Worker()构造函数,第一个参数是脚本的网址(必须遵守同源政策),该参数是必需的,且只能加载 JS 脚本,否则报错。第二个参数是配置对象,该对象可选。它的一个作用就是指定 Worker 的名称,用来区分多个 Worker 线程。

        • 主线程代码示例:
        <!--主线程,HTML文件的body标签中-->
        <div>
            myworker 输出内容:
            <span id='app'></span>
            <input type='text' title='' id='msg'>
            <button onclick='sendMessage()'>
                    发送
            </button>
            <button onclick='stopWorker()'>
                  stop!
            </button>
        </div>
        <script type='text/javascript'>
          // 使用Worker前检查一下浏览器是否支持
              if ( typeof ( Worker ) === 'undefined') 
                  document.writeln(' Sorry! No Web Worker support.. ')
              else {
                  // 创建 子线程 worker
                  let worker = new Worker('webWork.js', { name : 'worker' });
                  // 主线程 给 子线程 发消息
                  worker.postMessage('Hello World');
                  // worker.postMessage({method: 'echo', args: ['Work']});
        
                  // 主线程 监听 子线程 发的消息
                  worker.onmessage = function (event) {
                        console.log('Received message ' + event.data);
                        doSomething();
                  }
                  function doSomething() {
                        // 执行任务 通知 子线程调用结束
                        worker.postMessage('Work done!');
                        // 使用 完毕 关闭 子线程
                        worker.terminate();
                  }
        
                  // 子线程 myworker
                  let myworker = new Worker('webWork2.js', { name : 'myworker'                   });
                // myworker 进行计算 监听返回的结果 显示在页面上
                myworker.onmessage = (e) => {
                      document.getElementById("app").innerHTML = e.data
                }
        
                // 点击停止 按钮 通知 mywork 停止计算
                function stopWorker () {
                      myworker.postMessage('stop');
                      // myworker 完成任务以后,主线程就可以把它关掉。
                      myworker.terminate();
                }
        
          }
          </script>
        
      • Worker线程

        • Worker线程代码示例:
          // webWork.js
        
          console.log(self.name,'webWork.js');
        
        // self代表子线程自身,即子线程的全局对象。
        
        // // 可以自己写监听
        // self.addEventListener('message', function (e) {
        //   console.log(e.data,'data')
        //   self.postMessage('You said: ' + e.data);
        // }, false);
        
        // 也可以使用 onmessage 来进行监听
        self.onmessage = (e) => {
            console.log(e.data,'e')
        self.postMessage('You said: ' + e.data);
        if (e.data == 'Work done!') {
          self.close();
          }
        }
        
         // webWork2.js
        
         console.log(self.name,'webWork2.js');
        
        // 定时器 计算值 返回给 主线程
        let i = 1;
        function add () {
         i ++;
         self.postMessage(i);
         setTimeout(add, 1000);
        }
        
        add();
        
        // 监听主线程消息
        self.onmessage = (e) => {
           if (e.data == 'stop') {
               self.close();
           }
        }
        
        
      • Worker 加载脚本

        • Worker 内部如果要加载其他脚本,有一个专门的方importScripts()
        // 引用单个脚本
        importScripts('script1.js');
          
        // 引用多个脚本
        importScripts('script1.js', 'script2.js');
        
      • 错误处理

        • 主线程可以监听 Worker 是否发生错误。如果发生错误,Worker 会触发主线程的error事件。
            worker.onerror(function (event) {
            console.log([
            'ERROR: Line ', e.lineno, ' in ', e.filename, ': ', e.message
            ].join(''));
          });
          
          // 或者
          worker.addEventListener('error', function (event) {
              // ...
            });
          

        Worker 内部也可以监听error事件。

      • 关闭 Worker

        • 使用完毕,为了节省系统资源,必须关闭 Worker。

          // 主线程
          worker.terminate();
          
          // Worker 线程
          self.close();
          
  5. 数据通信

    • 主线程与 Worker 之间的通信内容,可以是文本,也可以是对象。需要注意的是,这种通信是拷贝关系,即是传值而不是传址,Worker 对通信内容的修改,不会影响到主线程。事实上,浏览器内部的运行机制是,先将通信内容串行化,然后把串行化后的字符串发给 Worker,后者再将它还原。

      主线程与 Worker 之间也可以交换二进制数据,比如 File、Blob、ArrayBuffer 等类型,也可以在线程之间发送。但是,拷贝方式发送二进制数据,会造成性能问题。比如,主线程向 Worker 发送一个 500MB 文件,默认情况下浏览器会生成一个原文件的拷贝。为了解决这个问题,JavaScript 允许主线程把二进制数据直接转移给子线程,但是一旦转移,主线程就无法再使用这些二进制数据了,这是为了防止出现多个线程同时修改数据的麻烦局面。这种转移数据的方法,叫做Transferable Objects。这使得主线程可以快速把数据交给 Worker,对于影像处理、声音处理、3D 运算等就非常方便了,不会产生性能负担。

      • 如果要直接转移数据的控制权,就要使用下面的写法。

        // Transferable Objects 格式
        worker.postMessage(arrayBuffer, [arrayBuffer]);
        
        // 例子
        var ab = new ArrayBuffer(1);
        worker.postMessage(ab, [ab]);
        
  6. 常用的API

    • 主线程中的API:
      • worker.postMessage: 主线程往worker线程发消息,消息可以是任意类型数据,包括二进制数据
      • worker.terminate: 主线程关闭worker线程
      • worker.onmessage: 指定worker线程发消息时的回调,也可以通过worker.addEventListener('message',cb)的方式
      • worker.onerror: 指定worker线程发生错误时的回调,也可以worker.addEventListener('error',cb)
    • Worker线程中的API:
      • self.postMessage: worker线程往主线程发消息,消息可以是任意类型数据,包括二进制数据
      • self.close: worker线程关闭自己
      • self.onmessage: 指定主线程发worker线程消息时的回调,也可以self.addEventListener('message',cb)
      • self.onerror: 指定worker线程发生错误时的回调,也可以 self.addEventListener('error',cb)
  7. 实战场景

    • 轮询

      有时,浏览器需要轮询服务器状态,以便第一时间得知状态改变。这个工作可以放在 Worker 里面。

      function createWorker(f) {
        var blob = new Blob(['(' + f.toString() +')()']);
        var url = window.URL.createObjectURL(blob);
        var worker = new Worker(url);
        return worker;
      }
      
      var pollingWorker = createWorker(function (e) {
        var cache;
      
        function compare(new, old) { ... };
      
        setInterval(function () {
          fetch('/my-api-endpoint').then(function (res) {
            var data = res.json();
      
            if (!compare(data, cache)) {
              cache = data;
              self.postMessage(data);
            }
          })
        }, 1000)
      });
      
      pollingWorker.onmessage = function () {
        // render data
      }
      
      pollingWorker.postMessage('init');
      
  • 加密数据

    有些加解密的算法比较复杂,或者在加解密很多数据的时候,这会非常耗费计算资源,导致UI线程无响应,因此这是使用Web Worker的好时机,使用Worker线程可以让用户更加无缝的操作UI。

  • 预取数据

    有时候为了提升数据加载速度,可以提前使用Worker线程获取数据,因为Worker线程是可以是用 XMLHttpRequest 的。

  • 预渲染

    在某些渲染场景下,比如渲染复杂的canvas的时候需要计算的效果比如反射、折射、光影、材料等,这些计算的逻辑可以使用Worker线程来执行,也可以使用多个Worker线程。

  • 复杂数据处理场景

    某些检索、排序、过滤、分析会非常耗费时间,这时可以使用Web Worker来进行,不占用主线程。

  • 预加载图片

    有时候一个页面有很多图片,或者有几个很大的图片的时候,如果业务限制不考虑懒加载,也可以使用Web Worker来加载图片。

    // 主线程
    let w = new Worker("js/workers.js");
    w.onmessage =
      function (event) {
        var img = document.createElement("img");
        img.src = window.URL.createObjectURL(event.data);
        document.querySelector('#result').appendChild(img);
      };
    // worker线程
    let arr = [...图片路径];
    for (let i = 0, len = arr.length; i < len; i++) {
      let req = new XMLHttpRequest();
      req.open('GET', arr[i], true);
      req.responseType = "blob";
      req.setRequestHeader("client_type", "DESKTOP_WEB");
      req.onreadystatechange = () => {
        if (req.readyState == 4) {
          postMessage(req.response);
        }
      };
      req.send(null);
    }
    
  1. 注意事项

    • 虽然使用worker线程不会占用主线程,但是启动worker会比较耗费资源

    • 主线程中使用XMLHttpRequest在请求过程中浏览器另开了一个异步http请求线程,但是交互过程中还是要消耗主线程资源

  2. 参考链接

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,080评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,422评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,630评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,554评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,662评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,856评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,014评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,752评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,212评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,541评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,687评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,347评论 4 331
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,973评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,777评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,006评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,406评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,576评论 2 349

推荐阅读更多精彩内容

  • 一、概述 JavaScript 语言采用的是单线程模型,也就是说,所有任务只能在一个线程上完成,一次只能做一件事。...
    零星小雨_c84a阅读 2,454评论 0 2
  • 作者:阮一峰www.ruanyifeng.com/blog/2018/07/web-worker.html 概述 ...
    grain先森阅读 1,077评论 0 1
  • 在web开发中,前端是单线程操作的,当前端有大运算任务时,前端会出现卡顿的情况,影响页面性能。为了将任务在后台操作...
    jshan阅读 636评论 0 0
  • 「一」 沈若黎病了,很严重。 感冒发烧,好久都没醒。 好了之后,也一直什么都不说,神情忧郁。 发生了什么?...
    恋爱小苏打阅读 575评论 0 8
  • 谢谢,已经不需要了…… 无论是宽恕别人也好 还是让自己解脱也罢 我都觉得丝毫没有这个必要了 就像以后你再出现 可我...
    金澜爱写作阅读 97评论 0 0