热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

JavaScript工作原理之七-WebWorkers分类及5个使用场景

WebWorkers分类及5个使用场景原文请查阅这里,略有删减,本文采用知识共享署名4.0国际许可协议共享,BYTroland。这是JavaScript工作原理的第七章。本系列持续
Web Workers 分类及 5 个使用场景

原文请查阅
这里,略有删减,本文采用
知识共享署名 4.0 国际许可协议共享,BY
Troland。

这是 Javascript 工作原理的第七章。

本系列持续更新中,Github 地址请查阅这里。

现在,我们将会剖析 Web Workers:我们将会综合比较不同类型的 workers,如何组合运用他们的构建模块来进行开发以及不同场景下各自的优缺点。最后,我们将会介绍 5 个 Web Workder 的使用场景。

在前面的详细介绍的文章中你已经清楚地了解到 Javascript 是单线程的事实。然而,Javascript 也允许开发者编写异步代码。

异步编程的局限性

前面我们了解到异步编程及其使用时机。

异步编程通过调度部分代码使之在事件循环中延迟执秆,这样就允许优先渲染程序界面,从而让程序运行流畅。

AJAX 请求是一个很好的异步编程的使用场景 。因为请求可能会花很长的时间,所以可以异步执行它们,然后在客户端等待数据返回的同时,运行其它代码。

// 假设使用 jQuery
jQuery.ajax({
url: 'https://api.example.com/endpoint',
success: function(response) {
// 当数据返回时候的代码
}
});

然而,这里会产生一个问题-AJAX 请求是由浏览器网页 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 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 // 在事件循环中调用下一个函数
setTimeout(function() {
sum += numbers[i];
calculateSumAsync(i + 1);
}, 0);
} else {
// 到达数组末尾,调用回调
callback(sum / len);
}
}
calculateSumAsync(0);
}

这里利用 setTimeout 函数在事件循环中循序添加每一次计算。在每一次计算之间,将会有充足的时间来进行其它的计算和解冻浏览器。

Web Workders 来救场

HTML5 给我们带了很多开箱即用的好用的功能,包括:

  • SSE(之前文章中提到过并且和 WebSockets 进行了比较)
  • Geolocation
  • Application cache
  • Local Storage
  • Drag and Drop
  • Web Workers

Web Workers 是浏览器内置的线程所以可以被用来执行非阻塞事件循环的 Javascript 代码。

屌爆了。整个 Javascript 是基于单线程环境的而 Web Workers (部分)可以突破这方面的限制。

Web Workers 允许开发者把长时间运行和密集计算型的任务放在后台执行而不会阻塞 UI,这会使得应用程序运行得更加流畅。另外,这样就不用再使用 setTimeout 的黑科技来防止阻塞事件循环了。

这里有一个展示使用和未使用 Web Workers 来进行数组排序的区别的示例。

Web Workers 概览

Web Workers 允许你做诸如运行处理 CPU 计算密集型任务的耗时脚本而不会阻塞 UI 的事情。事实上,所有这些操作都是并行执行的。Web Workers 是真正的多线程。

你或许会有疑问-『难道 Javascript 不是单线程的吗?』。

当你意识到 Javascript 是一门没有定义线程模型的语言的时候,或许你会感觉非常的惊讶。Web Workers 并不是 Javascript 的一部分,他们是可以通过 Javascript 进行操作的浏览器功能之一。以前,大多数的浏览器是单线程的(当然,现在已经变了),而且大多数的 Javascript 功能是在浏览器端实现完成的。Node.js 没有实现 Web Workers -它有 『cluster』和 『child_process』的概念,这两者和 Web Workers 有些许差异。

值得注意的是,规范中有三种类型的 Web Workers:

  • Dedicated Workers
  • Shared Workers
  • Service workers

Dedicated Workers

Dedicated Web Workers 是由主进程实例化并且只能与之进行通信

《Javascript 工作原理之七-Web Workers 分类及 5 个使用场景》

Dedicated Workers 浏览器支持情况

Shared Workers

Shared workers 可以被运行在同源的所有进程访问(不同的浏览的选项卡,内联框架及其它shared workers)。

《Javascript 工作原理之七-Web Workers 分类及 5 个使用场景》

Shared Workers 浏览器支持情况

Service Workers

Service Worker 是一个由事件驱动的 worker,它由源和路径组成。它可以控制它关联的网页,解释且修改导航,资源的请求,以及一种非常细粒度的方式来缓存资源以让你非常灵活地控制程序在某些情况下的行为(比如网络不可用)。

《Javascript 工作原理之七-Web Workers 分类及 5 个使用场景》

Service Workers 浏览器支持情况

本篇文章,我们将会专注于 Dedicated Workers 并以 『Web Workers』或者 『Workers』来称呼它。

Web Workers 运行原理

Web Workers 是以加载 .js 文件的方式实现的,这些文件会在页面中异步加载。这些请求会被 Web Worker API 完全隐藏。

Workers 使用类线程的消息传输-获取模式。它们非常适合于为用户提供最新的 UI ,高性能及流畅的体验。

Web Workers 运行于浏览器的一个隔离线程之中。因此,他们所执行的代码必须被包含在一个单独的文件之中。请谨记这一特性。

让我们看如何创建初始化 worker 吧:

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

如果 『task.js』文件存在且可访问,浏览器会生成一个线程来异步下载文件。当下载完成的时候,文件会立即执行然后 worker 开始运行。万一文件不存在,worker 会运行失败且没有任何提示。

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

worker.postMessage();

Web Worker 通信

为了在 Web Worker 和 创建它的页面间进行通信,你得使用 postMessage 方法或者一个广播信道。

postMessage 方法

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

让我们来看一个例子,通过往 worker 的方法的第一个参数传入更为复杂的 JSON 对象来理解其创建者页面是如何与之进行来回通信的。传入字符串与之类似。

让我们看下以下的 HTML 页面(或者更准确地说是 HTML 页面的一部分)


worker 的脚本如下:

self.addEventListener('message', function(e) {
var data = e.data;
switch (data.cmd) {
case 'average':
var result = calculateAverage(data); // 某个数值数组中计算平均值的函数
self.postMessage(result);
break;
default:
self.postMessage('Unknown command');
}
}, false);

当点击按钮,会在主页面调用 postMessage 方法。

worker.postMessage 行代码会把包含 cmddata 属性及其各自属性值的 JSON 对象传入 worker。worker 通过定义监听 message 事件来处理传过来的消息。

当接收到消息的时候,worker 会执行实际的计算而不会阻塞事件循环。worker 会检查传进来的 e 事件,然后像一个标准的 Javascript 函数那样运行。当运行结束,传回主页面计算结果。

在 worker 的上下文中,selfthis 都指向 worker 的全局作用域。

有两种方法来中断 woker 的执行:主页面中调用
worker.terminate() 或者在 workder 内部调用
self.close()

广播信道

Broadcast Channel 是更为普遍的通信接口。它允许我们向共享同一个源的所有上下文发送消息。同一个源下的所有的浏览器选项卡,内联框架或者 workers 都可以发送和接收消息:

// 连接到一个广播信道
var bc = new BroadcastChannel('test_channel');
// 发送简单信息示例
bc.postMessage('This is a test message.');
// 一个在控制台打印消息的简单事件处理程序示例
// logs the message to the console
bc.Onmessage= function (e) {
console.log(e.data);
}
// 关闭信道
bc.close()

视觉上看,你可以通过广播信道的图例以更加深刻的理解它。

《Javascript 工作原理之七-Web Workers 分类及 5 个使用场景》

所有的浏览器上下文都是同源的

然而,广播信道浏览器兼容性不太好:

《Javascript 工作原理之七-Web Workers 分类及 5 个使用场景》

消息大小

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

  • 复制消息:消息被序列化,复制,然后发送出去,接着在接收端反序列化。页面和 worker 没有共享一个相同的消息实例,所以在每次传递消息过程中最后的结果都是复制的。大多数浏览器是通过在任何一端自动进行 JSON 编码/解码消息值来实现这一功能。正如所预料的那样,这些对于数据的操作显著增加了消息传送的性能开销。消息越大,传送的时间越长。
  • 消息传输:这意味着最初的消息发送者一发送即不再使用()。数据传输非常的快。唯一的限制即只能传输 ArrayBuffer 数据对象。

Web Workers 的可用功能

由于 Web Workers 的多线程特性,它只能使用一部分 Javascript 功能。以下是可使用的功能列表:

  • navigator 对象
  • location 对象(只读)
  • XMLHttpRequest
  • setTimeout()/clearTimeout()setInterval()/clearInterval()
  • Application Cache
  • 使用 importScripts 来引用外部脚本
  • 创建其它 web workers

Web Worker 的局限性

令人沮丧的是,Web Workers 不能够访问一些非常关键的 Javascript 功能:

  • DOM(非线程安全的)
  • window 对象
  • document 对象
  • parent 对象

这意味着 Web Worker 不能够操作 DOM(因此不能更新 UI)。有时候,这会让人很蛋疼,不过一旦你学会如何合理地使 Web Workers,你就会把它当成单独的『计算机器』来使用而用其它页面代码来操作 UI。Workers 将会为你完成繁重的计算任务然后一旦任务完成,会把结果传到页面中并对界面进行必要的更新。

错误处理

和任何 Javascript 代码一样,你会想要处理 Web Workers 中的任何错误。当在 worker 执行过程中有错误发生的时候,会触发 ErrorEvent 事件。这个接口包含三个有用的属性来指出错误的地方:

  • filename-引起错误的 worker 脚本名称
  • 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(); // 启动 worker 而不带任何消息

self.addEventListener('message', function(e) {
postMessage(x * 2); // 意图错误. 'x' 未定义
};

这里,你可以看到我们创建了一个 worker 然后开始监听 error 事件。

在 worker 中(在 workerWithError 中),我们通过未在作用域中定义的 x 乘以 2 来创建一个意图错误。异常会传播到初始化脚本(即主页面中)然后调用 onError 并传入关于错误的信息。

Web Workers 最佳使用场景

迄今为止,我们列举了 Web Workers 的长处及其限制。让我们看看他们的最佳使用场景:

  • 射线追踪:射线追踪是一项通过追踪光线的路径作为像素来生成图片的渲染技术。Ray tracing 使用 CPU 密集型计算来模仿光线的路径。思路即模仿一些诸如反射,折射,材料等的效果。所有的这些计算逻辑可以放在 Web Worker 中以避免阻塞 UI 线程。甚至更好的方法即-你可以轻易地把把图片的渲染拆分在几个 workers 中进行(即在各自的 CPU 中进行计算,意思是说利用多个 CPU 来进行计算,可以参考下 nodejs 的 api)。这里有一个使用 Web Workers 来进行射线追踪的简单示例-https://nerget.com/rayjs-mt/r…。
  • 加密:端到端的加密由于对保护个人和敏感数据日益严格的法律规定而变得越来越流行。加密有时候会非常地耗时,特别是如果当你需要经常加密很多数据的时候(比如,发往服务器前加密数据)。这是一个使用 Web Worker 的绝佳场景,因为它并不需要访问 DOM 或者利用其它魔法-它只是纯粹使用算法进行计算而已。一旦在 worker 进行计算,它对于用户来说是无缝地且不会影响到用户体验。
  • 预取数据:为了优化网站或者网络应用及提升数据加载时间,你可以使用 Workers 来提前加载部分数据以备不时之需。不像其它技术,Web Workers 在这种情况下是最棒哒,因为它不会影响程序的使用体验。
  • 渐进式网络应用:即使在网络不稳定的情况下,它们必须快速加载。这意味着数据必须本地存储于浏览器中。这时候 IndexDB 及其它类似的 API 就派上用场了。大体上说,一个客户端存储是必须的。为了不阻塞 UI 线程的渲染,这项工作必须由 Web Workers 来执行。呃,当使用 IndexDB的时候,可以不使用 workers 而使用其异步接口,但是之前它也含有同步接口(可能会再次引入 ),这时候就必须在 workers 中使用 IndexDB。

    这里需要注意的是在现代浏览器已经不支持同步接口了,具体可查看这里。

  • 拼写检查:一个基本的拼写检测器是这样工作的-程序会读取一个包含拼写正确的单词列表的字典文件。字典会被解析成一个搜索树以加快实际的文本搜索。当检查器检查一个单词的时候,程序会在预构建搜索树中进行检索。如果在树中没有检索到,则会通过提供替代的字符为用户提供替代的拼写并检测单词是否是有效-是否是用户需要的单词。这个检索过程中的所有工作都可以交由 Web Worker 来完成,这样用户就只需输入单词和语句而不会阻塞 UI,与此同时 worker 会处理所有的搜索和服务建议。

在 SessionStack 中对于我们来说性能和可靠性是至关重要的。之所以这么重要的原因是一旦把 SessionStack 整合进网络应用,它就会开始收集从 DOM 变化,用户交互到网络请求,未处理异常和调试信息的所有一切信息。所有的数据都是即时传输到我们的服务器的,这样就允许你以视频的方式重放网络应用中的所有问题以及观察用户端产生的一切问题。所有的一切都只会给你的程序带来极小的延迟且没有任何的性能开销。

这就是为什么我们使用 Web Workers 来处理监视库和播放器的逻辑的原因,因为 Web Workers 会帮我们处理诸如使用哈希来验证数据完整性,渲染等 CPU 密集型的任务。

在这个网络技术日新月异的时代,我们更加努力地保证 SessionStack 轻巧且不会给用户程序带来任何性能影响。

扩展

实际工作过程会遇到用户需要通过解析远程图片来获得图片 base64 的案例,那么这时候,如果图片非常大,就会造成 canvas 的 toDataURL 操作相当的耗时,从而阻塞页面的渲染。

所以解决思路即把这里的处理图片的操作交由 worker 来处理。以下贴出主要的代码:










以上是通过 canvas 来获取图片数据,那么是否有其它方法呢?肯定有的啦,动下脑筋吧少年。

本系列持续更新中,Github 地址请查阅这里。


推荐阅读
  • Nginx使用(server参数配置)
    本文介绍了Nginx的使用,重点讲解了server参数配置,包括端口号、主机名、根目录等内容。同时,还介绍了Nginx的反向代理功能。 ... [详细]
  • VScode格式化文档换行或不换行的设置方法
    本文介绍了在VScode中设置格式化文档换行或不换行的方法,包括使用插件和修改settings.json文件的内容。详细步骤为:找到settings.json文件,将其中的代码替换为指定的代码。 ... [详细]
  • CSS3选择器的使用方法详解,提高Web开发效率和精准度
    本文详细介绍了CSS3新增的选择器方法,包括属性选择器的使用。通过CSS3选择器,可以提高Web开发的效率和精准度,使得查找元素更加方便和快捷。同时,本文还对属性选择器的各种用法进行了详细解释,并给出了相应的代码示例。通过学习本文,读者可以更好地掌握CSS3选择器的使用方法,提升自己的Web开发能力。 ... [详细]
  • 本文介绍了Java工具类库Hutool,该工具包封装了对文件、流、加密解密、转码、正则、线程、XML等JDK方法的封装,并提供了各种Util工具类。同时,还介绍了Hutool的组件,包括动态代理、布隆过滤、缓存、定时任务等功能。该工具包可以简化Java代码,提高开发效率。 ... [详细]
  • android listview OnItemClickListener失效原因
    最近在做listview时发现OnItemClickListener失效的问题,经过查找发现是因为button的原因。不仅listitem中存在button会影响OnItemClickListener事件的失效,还会导致单击后listview每个item的背景改变,使得item中的所有有关焦点的事件都失效。本文给出了一个范例来说明这种情况,并提供了解决方法。 ... [详细]
  • 如何使用Java获取服务器硬件信息和磁盘负载率
    本文介绍了使用Java编程语言获取服务器硬件信息和磁盘负载率的方法。首先在远程服务器上搭建一个支持服务端语言的HTTP服务,并获取服务器的磁盘信息,并将结果输出。然后在本地使用JS编写一个AJAX脚本,远程请求服务端的程序,得到结果并展示给用户。其中还介绍了如何提取硬盘序列号的方法。 ... [详细]
  • 本文由编程笔记#小编为大家整理,主要介绍了logistic回归(线性和非线性)相关的知识,包括线性logistic回归的代码和数据集的分布情况。希望对你有一定的参考价值。 ... [详细]
  • 本文介绍了如何使用PHP向系统日历中添加事件的方法,通过使用PHP技术可以实现自动添加事件的功能,从而实现全局通知系统和迅速记录工具的自动化。同时还提到了系统exchange自带的日历具有同步感的特点,以及使用web技术实现自动添加事件的优势。 ... [详细]
  • 生成式对抗网络模型综述摘要生成式对抗网络模型(GAN)是基于深度学习的一种强大的生成模型,可以应用于计算机视觉、自然语言处理、半监督学习等重要领域。生成式对抗网络 ... [详细]
  • 在Android开发中,使用Picasso库可以实现对网络图片的等比例缩放。本文介绍了使用Picasso库进行图片缩放的方法,并提供了具体的代码实现。通过获取图片的宽高,计算目标宽度和高度,并创建新图实现等比例缩放。 ... [详细]
  • 本文介绍了在开发Android新闻App时,搭建本地服务器的步骤。通过使用XAMPP软件,可以一键式搭建起开发环境,包括Apache、MySQL、PHP、PERL。在本地服务器上新建数据库和表,并设置相应的属性。最后,给出了创建new表的SQL语句。这个教程适合初学者参考。 ... [详细]
  • 云原生边缘计算之KubeEdge简介及功能特点
    本文介绍了云原生边缘计算中的KubeEdge系统,该系统是一个开源系统,用于将容器化应用程序编排功能扩展到Edge的主机。它基于Kubernetes构建,并为网络应用程序提供基础架构支持。同时,KubeEdge具有离线模式、基于Kubernetes的节点、群集、应用程序和设备管理、资源优化等特点。此外,KubeEdge还支持跨平台工作,在私有、公共和混合云中都可以运行。同时,KubeEdge还提供数据管理和数据分析管道引擎的支持。最后,本文还介绍了KubeEdge系统生成证书的方法。 ... [详细]
  • 本文分享了一个关于在C#中使用异步代码的问题,作者在控制台中运行时代码正常工作,但在Windows窗体中却无法正常工作。作者尝试搜索局域网上的主机,但在窗体中计数器没有减少。文章提供了相关的代码和解决思路。 ... [详细]
  • 原文地址:https:www.cnblogs.combaoyipSpringBoot_YML.html1.在springboot中,有两种配置文件,一种 ... [详细]
  • JVM 学习总结(三)——对象存活判定算法的两种实现
    本文介绍了垃圾收集器在回收堆内存前确定对象存活的两种算法:引用计数算法和可达性分析算法。引用计数算法通过计数器判定对象是否存活,虽然简单高效,但无法解决循环引用的问题;可达性分析算法通过判断对象是否可达来确定存活对象,是主流的Java虚拟机内存管理算法。 ... [详细]
author-avatar
赵娜supergirl
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有