zoukankan      html  css  js  c++  java
  • [翻译]Review——How JavaScript works:The building blocks of Web Workers

    原文地址:https://blog.sessionstack.com/how-javascript-works-the-building-blocks-of-web-workers-5-cases-when-you-should-use-them-a547c0757f6a

    How JavaScript works: The building blocks of Web Workers + 5 cases when you should use them

    JavaScript的工作原理:Web Workers的构建块+当您应该使用它们时的5个案例

    您应该已经熟悉JavaScript在单个线程上运行的事实,正如我们之前非常详细地讨论过的那样。但是,JavaScript为开发人员提供了编写异步代码的机会。

    异步编程的局限性

    异步编程使您的应用程序UI能够及时响应,通过“调度”部分代码在事件循环中稍后执行,从而允许首先执行UI呈现。

    异步编程的一个很好的用例就是发出AJAX请求。由于请求可能需要花费大量时间,因此可以异步进行,并且在客户端等待响应时,可以执行其他代码。

    // This is assuming that you're using jQuery
    jQuery.ajax({
        url: 'https://api.example.com/endpoint',
        success: function(response) {
            // Code to be executed when a response arrives.
        }
    });

    但是,这会产生一个问题 - 请求由浏览器的WEB API处理,但其他代码如何异步?例如,如果成功回调内部的代码非常占用CPU,那该怎么办:

    var result = performCPUIntensiveCalculation();

    如果performCPUIntensiveCalculation不是HTTP请求而是阻塞代码(例如巨大的for循环),则无法释放事件循环并取消阻止浏览器的UI - 它将冻结并对用户无响应。

    这意味着异步函数只解决了JavaScript语言的一小部分单线程限制。

    在某些情况下,使用setTimeout可以在长时间运行的计算中取消阻止UI,从而获得良好的效果。例如,通过在单独的setTimeout调用中对复杂计算进行批处理,您可以将它们放在事件循环中的不同“位置”上,这样就可以花时间来执行UI呈现/响应。

    我们来看一个计算数值数组平均值的简单函数:

    function average(numbers) {
        var len = numbers.length,
            sum = 0,
            i;
    
        if (len === 0) {
            return 0;
        } 
        
        for (i = 0; i < len; i++) {
            sum += numbers[i];
        }
       
        return sum / len;
    }

    这就是你如何重写上面的代码并“模拟”异步性:

    function averageAsync(numbers, callback) {
        var len = numbers.length,
            sum = 0;
    
        if (len === 0) {
            return 0;
        } 
    
        function calculateSumAsync(i) {
            if (i < len) {
                // Put the next function call on the event loop.
                setTimeout(function() {
                    sum += numbers[i];
                    calculateSumAsync(i + 1);
                }, 0);
            } else {
                // The end of the array is reached so we're invoking the callback.
                callback(sum / len);
            }
        }
    
        calculateSumAsync(0);
    }

    这将使用setTimeout函数,该函数将在事件循环中进一步添加计算的每个步骤。在每次计算之间,将有足够的时间进行其他计算,这是解冻浏览器所必需的。

    Web Workers将拯救你

    HTML5为我们带来了许多开箱即用的好东西,包括:

    • SSE
    • Geolocation
    • Application cache
    • Local Storage
    • Drag and Drop
    • Web Workers

    Web Workers是浏览器中的线程,可用于执行JavaScript代码而不会阻止事件循环。

    这真是太神奇了。 JavaScript的整个范例都是基于单线程环境的思想,但Web Workers可以删除(部分)这种限制。

    Web Workers允许开发人员在后台运行长时间运行且计算密集的任务,而不会阻止UI,从而使您的应用程序更具响应性。更重要的是,为了破解事件循环的方式,不需要使用setTimeout的技巧。

    这是一个简单的演示,显示了使用和不使用Web Workers对数组进行排序的区别。

    Web Worker概述

    Web Workers允许您执行一些操作,例如启动长时间运行的脚本来处理计算密集型任务,但不会阻止UI。实际上,这一切都是并行发生的。 Web Workers是真正的多线程。

    您可能会说 - “JavaScript不是单线程语言吗?”。

    当你意识到JavaScript是一种没有定义线程模型的语言时,这应该是你的“啊哈!”时刻。 Web Workers不是JavaScript的一部分,它们是可以通过JavaScript访问的浏览器功能。大多数浏览器历来都是单线程的(当然,这已经改变了),并且大多数JavaScript实现都发生在浏览器中。 Web Worker没有在Node.JS中实现 - 它有一个“cluster”或“child_process”的概念,它有点不同。

    值得注意的是,该规范提到了三种类型的Web Worker:

    • Dedicated Workers
    • Shared Workers
    • Service workers

    Dedicated Workers(专用Workers)

    专用Web Worker由主进程实例化,只能与之通信。

    Shared Workers(共享Workers)

    在同一来源(不同的浏览器选项卡,iframe或其他共享工作者)上运行的所有进程都可以访问共享工作者。

    Service Workers(服务Workers)

    服务工作者是针对原点和路径注册的事件驱动的工作者。它可以控制与之关联的网页/网站,拦截和修改导航和资源请求,以及以非常精细的方式缓存资源,让您可以很好地控制应用在特定情况下的行为(例如,当网络不是可用。)

    在这篇文章中,我们将专注于“专用Workers”,并将他们称为“Web Workers”或“Workers”。

    Web Worker是如何工作的

    Web Workers是以.js的文件形式来呈现的,这些文件通过页面中的异步HTTP请求被引进来。 Web Worker API完全隐藏了这些请求。

    Workers利用类似线程的消息传递来实现并行性。它们非常适合用户保持UI最新,高性能和响应性。

    Web Workers在浏览器中的独立线程中运行。因此,它们执行的代码需要包含在单独的文件中。记住这一点非常重要。

    让我们看看如何创建基本的Worker:

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

    如果“task.js”文件存在且可访问,则浏览器将生成一个新线程,该线程异步下载该文件。下载完成后,它将立即执行,worker将开始。
    如果提供的文件路径返回404,则工作程序将以静默方式失败。

    要启动创建的worker,需要调用postMessage方法:

    worker.postMessage();

    Web Worker通信

    为了在Web Worker和创建它的页面之间进行通信,您需要使用postMessage方法或广播通道。

    postMessage方法

    较新的浏览器支持JSON对象作为方法的第一个参数,而较旧的浏览器仅支持字符串。

    让我们看一个示例,通过将JSON对象作为一个更“复杂”的示例传递,创建工作者的页面如何与之来回通信。传递一个字符串是完全一样的。

    让我们看看下面的HTML页面(或者更精确的部分):

    <button onclick="startComputation()">Start computation</button>
    
    <script>
      function startComputation() {
        worker.postMessage({'cmd': 'average', 'data': [1, 2, 3, 4]});
      }
      var worker = new Worker('doWork.js');
      worker.addEventListener('message', function(e) {
        console.log(e.data);
      }, false);
      
    </script>

    这就是我们的Worker脚本:

    self.addEventListener('message', function(e) {
      var data = e.data;
      switch (data.cmd) {
        case 'average':
          var result = calculateAverage(data); // Some function that calculates the average from the numeric array.
          self.postMessage(result);
          break;
        default:
          self.postMessage('Unknown command');
      }
    }, false);

    单击该按钮时,将从主页面调用postMessage。 worker.postMessage行将JSON对象传递给worker,添加cmd和数据键及其各自的值。 worker将通过定义的消息处理程序处理该消息。

    当消息到达时,实际计算正在worker中执行,而不会阻止事件循环。worker正在检查传递的事件e并像标准JavaScript函数一样执行。完成后,结果将传递回主页面。

    在worker的上下文中,self和this都引用了worker的全局范围。

    有两种方法可以停止worker:通过从主进程中调用worker.terminate()或通过调用worker本身内部的self.close()。

    Broadcast Channel

    广播频道是一种更通用的通信API。它允许我们向共享相同来源的所有上下文广播消息。从同一来源提供的所有浏览器标签,iframe或worker都可以发送和接收消息:

    // Connection to a broadcast channel
    var bc = new BroadcastChannel('test_channel');
    
    // Example of sending of a simple message
    bc.postMessage('This is a test message.');
    
    // Example of a simple event handler that only
    // logs the message to the console
    bc.onmessage = function (e) { 
      console.log(e.data); 
    }
    
    // Disconnect the channel
    bc.close()

    在视觉上,您可以看到广播频道的外观更清晰:

    广播频道的浏览器支持有限:

    消息的大小

    有两种方法可以向Web Workers发送消息:

    • 复制消息:消息被序列化,复制,发送,然后在另一端反序列化。页面和worker不共享同一个实例,因此最终结果是每次传递都会创建一个副本。大多数浏览器通过自动JSON编码/解码任一端的值来实现此功能。正如所料,这些数据操作为消息传输增加了大量开销。消息越大,发送的时间越长。
    • 转移邮件:这意味着原始发件人一旦发送就无法再使用它。传输数据几乎是即时的。限制是只有ArrayBuffer可以转移。

    Web Workers可用的功能

    Web Workers由于具有多线程特性,因此只能访问JavaScript功能的子集。以下是功能列表:

    • The navigator object
    • The location object (read-only)
    • XMLHttpRequest
    • setTimeout()/clearTimeout() and setInterval()/clearInterval()
    • The Application Cache
    • Importing external scripts using importScripts()
    • Creating other web workers

    Web Worker的局限

    遗憾的是,Web Workers无法访问一些非常关键的JavaScript功能:

    • The DOM (it’s not thread-safe)
    • The window object
    • The document object
    • The parent object

    这意味着Web Worker无法操纵DOM(以及UI)。它有时会很棘手,但是一旦你学会了如何正确使用Web Workers,你就会开始将它们作为单独的“计算机器”使用,而所有的UI更改都将在你的页面代码中进行。Worker将为您完成所有繁重的工作,一旦完成工作,您将把结果传递给对UI进行必要更改的页面。

    处理错误

    与任何JavaScript代码一样,您将需要处理Web Workers中引发的任何错误。如果在执行worker时发生错误,则会触发ErrorEvent。该界面包含三个有用的属性,用于确定出现了什么问题:

    • filename - 导致错误的工作程序脚本的名称
    • lineno - 发生错误的行号
    • message - 错误的描述

    以下是示例:

    function onError(e) {
      console.log('Line: ' + e.lineno);
      console.log('In: ' + e.filename);
      console.log('Message: ' + e.message);
    }
    
    var worker = new Worker('workerWithError.js');
    worker.addEventListener('error', onError, false);
    worker.postMessage(); // Start worker without a message.
    self.addEventListener('message', function(e) {
      postMessage(x * 2); // Intentional error. 'x' is not defined.
    };

    在这里,您可以看到我们创建了一个worker并开始侦听错误事件。

    在worker内部(在workerWithError.js中),我们通过将x乘以2来创建故意异常,而x未在该范围中定义。异常传播到初始脚本,并且正在调用onError以及有关错误的信息。

    Web Worker的好用例

    到目前为止,我们已经列出了Web Workers的优势和局限性。现在让我们看看它们最强大的用例是什么:

    • 光线跟踪:光线跟踪是一种渲染技术,用于通过将光线路径跟踪为像素来生成图像。光线跟踪使用非常耗费CPU的数学计算来模拟光的路径。这个想法是模拟一些效果,如反射,折射,材料等。所有这些计算逻辑都可以添加到Web Worker中,以避免阻塞UI线程。更好的是 - 您可以轻松地在几个工作者(以及几个CPU之间)之间分割图像渲染。以下是使用Web Workers进行光线跟踪的简单演示 - https://nerget.com/rayjs-mt/rayjs.html。
    • 加密:由于对个人和敏感数据的监管越来越严格,端到端加密越来越受欢迎。加密可能是非常耗时的事情,特别是如果有大量数据必须经常加密(例如,在将其发送到服务器之前)。这是一个非常好的场景,可以使用Web Worker,因为它不需要任何访问DOM或任何花哨的东西 - 它是纯粹的算法来完成它们的工作。一旦进入工作人员,它对最终用户是无缝的,并且不会影响他们的体验。
    • 预取数据:为了优化您的网站或Web应用程序并缩短数据加载时间,您可以利用Web Workers提前加载和存储一些数据,以便以后在需要时使用它。在这种情况下,Web Workers非常棒,因为它们不会影响应用程序的UI,这与没有工作人员的情况不同。
    • 渐进式Web应用程序:即使网络连接不稳定,也必须快速加载。这意味着数据必须本地存储在浏览器中。这是IndexDB或类似API发挥作用的地方。基本上,需要客户端存储。为了在不阻塞UI线程的情况下使用,必须在Web Workers中完成工作。好吧,在IndexDB的情况下,有一个异步API,即使没有工作人员也允许你这样做,但之前有一个同步API(可能会再次引入),它只能在worker中使用。
    • 拼写检查:基本的拼写检查工具按以下方式工作 - 程序读取带有正确拼写单词列表的字典文件。该字典被解析为搜索树,以使实际文本搜索有效。当向检查器提供单词时,程序检查它是否存在于预构建的搜索树中。如果在树中找不到该单词,则可以通过替换替换字符来为用户提供替代拼写,并测试它是否是有效单词 - 如果它是用户想要写的单词。所有这些处理都可以轻松卸载到Web Worker,这样用户只需键入单词和句子而不会阻止UI,而工作人员可以执行所有搜索和提供建议。

    —— 完 ——

    
    
  • 相关阅读:

    模块
    序列化模块
    time模块、os模块、sys模块
    re模块、collections模块、random模块
    正则表达式
    递归函数和二分查找
    匿名函数
    推推导式和内置函数
    Java引用类型与值类型——Java面向对象基础(7)
  • 原文地址:https://www.cnblogs.com/bbcfive/p/10665773.html
Copyright © 2011-2022 走看看