美文网首页前端
深入理解JavaScript中的Web Worker

深入理解JavaScript中的Web Worker

作者: HalShaw | 来源:发表于2019-08-04 17:07 被阅读0次
    Web Worker

    一、背景

    由于JavaScript是单线程模型,即所有任务只能在同一个线程上面完成,前面的任务没有做完,后面的就只能等待,这对于日益增强的计算能力来说不是一件好事。所以在HTML5中引入了Web Worker的概念,来为JavaScript创建多线程环境,将其中一些任务分配给Web Worker运行,二者可以同时运行,互不干扰,如图:


    With or without worker

    Web Worker是运行在后台的 JavaScript,独立于其他脚本,不会影响页面的性能。您可以继续做任何愿意做的事情:点击、选取内容等等,引入Web Worker的好处是一些计算密集型或高延迟的任务,被 Web Worker 线程所处理,主线程就会很流畅,不会被阻塞或拖慢,而此时 Web Worker 在后台运行。但是这也正是 Web Worker 比较耗费资源的原因,不应该过度使用,而且一旦使用完毕,就应该关闭。

    二、浏览器支持

    所有主流浏览器均支持 Worker,除了 Internet Explorer。
    可以使用如下代码检测 Worker 支持
    在创建 Worker 之前,请检测用户的浏览器是否支持它:

    if(typeof(Worker)!=="undefined")
      {
      // Yes! Web worker support!
      // Some code.....
      }
    else
      {
      // Sorry! No Web Worker support..
      }
    

    三、使用

    Web Worker 在独立线程中运行。因此,它们执行的代码需要保存在一个单独的文件中。但在保存代码前,我们要先在主线程上创建新的 Worker 对象。构造函数采用 Worker 脚本的名称:

    var worker = new Worker('worker.js');
    

    如果指定的异步下载文件存在,浏览器就会生成新的 Worker 线程。在完全下载并执行文件之前,系统不会生成 Worker。如果指向您 Worker 的路径返回 404,Worker 就会在不显示任何提示的情况下失败。

    创建 Worker 之后,通过调用 postMessage() 方法启动:

    worker.postMessage(); // Start the worker.
    
    • 传递变量,也可以给Worker传递一个变量
    // Create a new worker object
    var worker = new Worker('worker.js');
    
    // Send a message to start the worker and pass a variable to it
    var info = 'Web Workers';
    worker.postMessage(info);
    

    在Worker中,例如,在worker.js内部,可以使用onmessage事件来接收来自主线程的消息并且实现一些操作,可以通过event.data来获取到传递的变量:

    // Receive the message from the main thread
    onmessage = function(event) {
      // Do something
      var info = event.data;
    };
    

    从Worker 发送消息到主线程也是同样的方法(worker.js):

    // Receive the message from the main thread
    onmessage = function(event) {
      // Do something
      var info = event.data;
      var result = info + ' rise up!';
      postMessage(result);
    };
    

    主线程:

    // Create a new worker object
    var worker = new Worker('worker.js');
    
    // Send a message to start the worker and pass a variable to it
    var info = 'Web Workers';
    worker.postMessage(info);
    
    // Receive a message from the worker
    worker.onmessage = function (event) {
      // Do something
      alert(event.data);
    };
    

    也可以使用addEventListener来替换onmessage(JavaScript 高手们总是会推荐 addEventListener),例如:
    主线程:

    var worker = new Worker('doWork.js');
    
    worker.addEventListener('message', function(e) {
      console.log('Worker said: ', e.data);
    }, false);
    
    worker.postMessage('Hello World'); // Send data to our worker.
    

    doWork.js (Worker):

    self.addEventListener('message', function(e) {
      self.postMessage(e.data);
    }, false);
    

    在主线程和 Worker 之间传递的消息是复制而不是共享的。例如,下一示例中 JSON 消息的“msg”属性在两个位置中均可访问。即使对象运行在单独的专用空间中,系统似乎也会将其直接传递给 Worker。实际发生的情况是,系统将对象传递给 Worker 后,会将其序列化,随后在另一端解取消序列化。由于网页和 Worker 并不共享同一实例,因此每次传递时都要进行复制。大部分浏览器通过在任一端上对值进行自动 JSON 编码/解码来实施此功能。

    主线程:

    <button onclick="sayHI()">Say HI</button>
    <button onclick="unknownCmd()">Send unknown command</button>
    <button onclick="stop()">Stop worker</button>
    <output id="result"></output>
    
    <script>
      function sayHI() {
        worker.postMessage({'cmd': 'start', 'msg': 'Hi'});
      }
    
      function stop() {
        // Calling worker.terminate() from this script would also stop the worker.
        worker.postMessage({'cmd': 'stop', 'msg': 'Bye'});
      }
    
      function unknownCmd() {
        worker.postMessage({'cmd': 'foobard', 'msg': '???'});
      }
    
      var worker = new Worker('doWork2.js');
    
      worker.addEventListener('message', function(e) {
        document.getElementById('result').textContent = e.data;
      }, false);
    </script>
    

    doWork2.js

    self.addEventListener('message', function(e) {
      var data = e.data;
      switch (data.cmd) {
        case 'start':
          self.postMessage('WORKER STARTED: ' + data.msg);
          break;
        case 'stop':
          self.postMessage('WORKER STOPPED: ' + data.msg + '. (buttons will no longer work)');
          self.close(); // Terminates the worker.
          break;
        default:
          self.postMessage('Unknown command: ' + data.msg);
      };
    }, false);
    
    • 停止 Worker
      停止 Worker 的方法有两种:在主线程中调用 worker.terminate(),或在 Worker 本身内部调用 self.close()
    • Worker 作用域
      就 Worker 来说,self 和 this 指的都是 Worker 的全局作用域。因此,上一示例也可以写成:
    addEventListener('message', function(e) {
      var data = e.data;
      switch (data.cmd) {
        case 'start':
          postMessage('WORKER STARTED: ' + data.msg);
          break;
        case 'stop':
      ...
    }, false);
    

    四、加载外部脚本

    可以通过 importScripts() 函数将外部脚本文件或库加载到 Worker 中。该方法采用零个或多个字符串表示要导入的资源的文件名。

    此示例将 script1.js 和 script2.js 加载到了 Worker 中:

    importScripts('script1.js');
    importScripts('script2.js');
    

    也可以写成单个导入语句:

    importScripts('script1.js', 'script2.js');
    

    五、子 Worker

    Worker 可以生成子 Worker。这对于在运行时进一步拆分大任务来说非常重要。但是,子 Worker 还有几点注意事项:

    • 子 Worker 必须托管在与父线程相同的origin中。
    • 子 Worker 中的 URI 应相对于父 Worker 的位置进行解析(与主线程不同)。

    注意,大部分浏览器会为每个 Worker 生成单独的进程。在开始生成 Worker 场之前,请注意不要占用太多的用户系统资源。这样做的一个原因是,在主线程和 Worker 之间传递的消息是复制而不是共享的。

    六、处理错误

    与任何 JavaScript 逻辑一样,您需要处理 Web Worker 中出现的任何错误。如果在执行 Worker 时出现错误,就会触发 ErrorEvent。相关界面中包含用于找出错误内容的三个实用属性:filename - 导致错误的 Worker 脚本的名称;lineno - 出现错误的行号;以及 message - 有关错误的实用说明。以下示例设置了 onerror 事件处理程序以便打印错误内容:

    <output id="error" style="color: red;"></output>
    <output id="result"></output>
    
    <script>
      function onError(e) {
        document.getElementById('error').textContent = [
          'ERROR: Line ', e.lineno, ' in ', e.filename, ': ', e.message].join('');
      }
    
      function onMsg(e) {
        document.getElementById('result').textContent = e.data;
      }
    
      var worker = new Worker('workerWithError.js');
      worker.addEventListener('message', onMsg, false);
      worker.addEventListener('error', onError, false);
      worker.postMessage(); // Start worker without a message.
    </script>
    

    workerWithError.js

    self.addEventListener('message', function(e) {
      postMessage(1/x); // Intentional error.
    };
    

    七、适用和不适用 Worker 的功能

    由于 Web Worker 的多线程行为,所以它们只能使用 JavaScript 功能的子集:

    • navigator 对象
    • location 对象(只读)
    • XMLHttpRequest
    • setTimeout()/clearTimeout()setInterval()/clearInterval()
    • 应用缓存
    • 使用 importScripts() 方法导入外部脚本
    • 生成其他 Web Worker

    Worker 无法使用:

    • DOM(非线程安全)
    • window 对象
    • document 对象
    • parent 对象
    • alert()
    • confirm()
    • document.getElementById()

    八、更多使用方向

    • 预先抓取或缓存数据以便稍后使用
    • 突出显示代码语法或其他实时文本格式
    • 拼写检查
    • 分析视频或音频数据
    • 背景 I/O 或网络服务轮询
    • 处理较大数组或超大 JSON 响应
    • <canvas> 中的图片过滤
    • 更新本地网络数据库中的多行内容

    九、一些示例

    十、参考

    http://www.ruanyifeng.com/blog/2018/07/web-worker.html
    https://dev.opera.com/articles/web-workers-rise-up/
    https://www.html5rocks.com/en/tutorials/workers/basics/

    相关文章

      网友评论

        本文标题:深入理解JavaScript中的Web Worker

        本文链接:https://www.haomeiwen.com/subject/zlhydctx.html