GVKun编程网logo

web worker 实践(web实践总结)

15

本文的目的是介绍webworker实践的详细情况,特别关注web实践总结的相关信息。我们将通过专业的研究、有关数据的分析等多种方式,为您呈现一个全面的了解webworker实践的机会,同时也不会遗漏关

本文的目的是介绍web worker 实践的详细情况,特别关注web实践总结的相关信息。我们将通过专业的研究、有关数据的分析等多种方式,为您呈现一个全面的了解web worker 实践的机会,同时也不会遗漏关于2023 年的 Web Worker 项目实践、Ajax对Web套接字与Web Workers、ES6+Webpack 下使用 Web Worker、HTML5 WebApp part4:使用 Web Workers 来加速您的移动 Web 应用程序(上) ...的知识。

本文目录一览:

web worker 实践(web实践总结)

web worker 实践(web实践总结)

1.web worker

  在浏览器中JavaScript主线程与UI渲染线程是互斥的。即UI渲染线程会阻塞JavaScript线程的运行。

  web worker允许创建工作线程,并可以与JavaScript主线程同时运行,可以让一些占用大量计算资源的计算在worker线程上运行。

  worker线程的出现是为了,大量的计算占用javascript 主线程,导致 DOM操作阻塞。worker  线程一旦新建成功,就会一直运行,有利于随时响应主线程的通信,但这也会造成worker 线程比较消耗资源,所以在使用完成时,应该立即结束 worker 线程。

  出于安全和性能的考虑,worker 线程的运行有一下限制:

   (1)同源限制

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

  (2)DOM 操作限制

    worker 的执行上下文是一个最小化的运行环境,没有 DOM,window ,parent 等全局对象,只有最小化的 navigator,和只读的 location对象。

  (3)通信联系

    由于worker 与主线程的执行上下文不同,所以worker 线程与主线程之间的通信,只能通过 postMessage 来完成。

  (4)脚本限制

    worker线程不能执行 alter(),confirm(),方法,但是可以使用 XMLHttpRequert 对象发出 Ajax 请求。

  (5)文件限制

    worker 线程无法读取本地文件,它所加载的脚本必须来自网络,即不能打开本地文件系统(file://)。<1>由于 worker 需要一个 js文件来创建线程,而es6 中的模块无法直接引入一个js文件,所以在现在的 单页面应用中需要创建 worker 线程需要,引入 worker-loader 插件。worker-loader 加载js文件 来创建 worker。<2>由于worke线程中使用的文件必须来自网络,所以在 worker 线程中引入脚本或者库,需要 通过 importSript()来加载对应的脚本或者库。但是 webpack 打包后的应用模块,成为了一个个 chunk ,是无法通过对应的路径加载到对应的模块的,这时在应用来我worker-loader 的情况下,可以通过 require()或者 import 来加载对应的库,而不是通过 importScript().

  javaScript 允许主线程把二进制直接转移给子线程,但是一旦转移,主线程就无法使用这些二进制数据,这是为了防止不同线程同时修改数据会造成数据不一致性问题。

  

 1 //主线程与worker线程之间的通信
 2 //worker 可以利用XMLHttpRequest加载文件,
 3 
 4 var worker = new worker(''js/worker.js'');
 5 worker.postMessage(args);
 6 worker.onmessage = function(event){
 7      document.getElementById(''result'').textContent = event.data;};
  worker.terminate() // 结束 worker线程
8 9 //workerjs 10 onmessage = function(e){ 11 var calcuResult = e.data; 12 for(var i=0;i<1000;i++) 13 calcuResult+=i; 14 } 15 postMessage(calcuResult);

 

2023 年的 Web Worker 项目实践

2023 年的 Web Worker 项目实践

前言

Web Workers 是 2009 年就已经提案的老技术,但是在很多项目中的应用相对较少,常见一些文章讨论如何写 demo ,但很少有工程化和项目级别的实践,本文会结合 Web Workers 在京东羚珑的程序化设计项目中的实践,分享一下在当下的 2023 年,关于 worker 融入项目的一些思考和具体的实现方式,涉及到的 demo 已经放在 github 上附在文末,可供参考。

先简单介绍下 Web Workers,它是一种可以运行在 Web 应用程序后台线程,独立于主线程之外的技术。众所周知,JavaScript 语言是单线程模型的,而通过使用 Web Workers,我们可以创造多线程环境,从而可以发挥现代计算机的多核 CPU 能力,在应对规模越来越大的 Web 程序时也有较多收益。

Web Workers 宏观语义上包含了三种不同的 Worker: DedicatedWorker(专有worker)SharedWorker(共享Worker)ServiceWorker,本文讨论的是第一种,其他两种大家可以自行研究一下。

引入 Web Worker

当引入新技术时,通常我们会考虑的问题有:1、兼容性如何? 2、使用场景在哪?

问题 1,Web Workers 是 2009 年的提案,2012 年各大浏览器已经基本支持,11 年过去了,现在使用已经完全没有问题啦

caniuse

问题 2,主要考虑了以下 3 点:

  1. Worker API 的局限性:同源限制、无 DOM 对象、异步通信,因此适合不涉及 DOM 操作的任务
  2. Worker 的使用成本:创建时间 + 数据传输时间;考虑到可以预创建,可以忽略创建时间,只考虑数据传输成本,这里可参考 19 年的一个测试 Is postMessage slow ,简要结论是比较乐观的,大部分设备和数据情况下速度不是瓶颈
  3. 任务特点:需要是可并行的多任务,为了充分利用多核能力,可并行的任务数越接近 CPU 数量,收益会越高。多线程场景的收益计算,可以参考 Amdahl 公式,其中 F 是初始化所需比例,N 是可并行数:
    Amdahl公式

综上结论是,可并行的计算密集型任务适合用 Worker 来做。

不过 github 上我搜罗了一圈,也发现有一些不局限于此,颇有创意的项目,供大家打开思路:

  1. redux 挪到了 worker 内
  2. dom 挪到了 worker 内
  3. 可使用多核能力的框架

Worker 实践

介绍完 worker ,一个问题出现了:为什么一个兼容性良好,能够发挥并发能力的技术(听起来很有诱惑力),到现在还没有大规模使用呢?

我理解有 2 个原因:一是暂无匹配度完美的使用场景,因此引入被搁置了;二是 worker api 设计得太难用,参考很多 demo 看,限制多配置还麻烦,让人望而却步。本文会主要着力于第二点,希望给大家的 worker 实践提供一些成熟的工程化思路。

至于第一点理由,在如此卷的前端领域,当你手中已经有了一把好用的锤子,还找不到那颗需要砸的钉子吗?

Worker 到底有多难用

下面是一个原始 worker 的调用示例,上面是主线程文件,下面是 worker 文件:

// index.js
const worker = new Worker("./worker.js");
worker.onmessage = function (messageEvent) {
  console.log(messageEvent);
};
// worker.js
importScripts("constant.js");
function a() {
  console.log("test");
}

其中问题有:

  1. postMessage 传递消息的方式不适合现代编程模式,当出现多个事件时就涉及分拆解析和解决耦合问题,因此需要改造
  2. 新建 worker 需要单独文件,因此项目内需要处理打包拆分逻辑,独立出 worker 文件
  3. worker 内可支持定义函数,可通过importScript 方式引入依赖文件,但是都独立于主线程文件,依赖和函数的复用都需要改造
  4. 多线程环境必然涉及同步运行多个 worker,多 worker 的启动、复用和管理都需要自行处理

看完这么多问题,有没有感觉头很大,一个设计这样原始的 api,如何舒服的使用呢?

类库调研

首先可以想到的就是借助成熟类库的力量,下面表格是较为常见的几款 worker 类库,其中我们可能会关注的关键能力有:

  1. 通信是否有包装成更好用的方式,比如 promise 化或者 rpc
  2. 是否可以动态创建函数——可以增加 worker 灵活性
  3. 是否包含多 worker 的管理能力,也就是线程池
  4. 考虑 node 的使用场景,是否可以跨端运行

类库比较

比较之下,workerpool 胜出,它也是个年纪很大的库了,最早的代码提交在 6 年前,不过实践下来没有大问题,下文都会在使用它的基础上继续讨论。

有类库加持的 worker 现状

通过使用 workerpool,我们可以在主线程文件内新建 worker;它自动处理多 worker 的管理;可以执行 worker 内定义好的函数 a;可以动态创建一个函数并传入参数,让 worker 来执行。

// index.js
import workerpool from "workerpool";
const pool = workerpool.pool("./worker.js");
// 执行一个 worker 内定义好的函数
pool.exec("a", [1, 2]).then((res) => {
  console.log(res);
});
// 执行一个自定义函数
pool
  .exec(
    (x, y) => {
      return x + y;
    }, // 自定义函数体
    [1, 2] // 自定义函数参数
  )
  .then((res) => {
    console.log(res);
  });
// worker.js
importScripts("constant.js");
function a() {
  console.log("test");
}

但是这样还不够,为了可以舒适的写代码,我们需要进一步改造

向着舒适无感的 worker 编写前进

我们期望的目标是:

  1. 足够灵活:可以随意编写函数,今天我想计算1+1,明天我想计算1+2,这些都可以动态编写,最好它可以直接写在主线程我自己的文件里,不需要我跑到 worker 文件里去改写
  2. 足够强大:我可以使用公共依赖,比如 lodash 或者是项目里已经定义好的某些公共函数

考虑到 workerpool 具备了动态创建函数的能力,第一点已经可以实现;而第二点关于依赖的管理,则需要自行搭建,接下来介绍搭建步骤

  1. 抽取依赖,管理编译和更新:

新增一个依赖管理文件worker-depts.js,可按照路径作为 key 名构建一个聚合依赖对象,然后在 worker 文件内引入这份依赖

// worker-depts.js
import * as _ from "lodash-es";
import * as math from "../math";

const workerDepts = {
  _,
  "util/math": math,
};

export default workerDepts;
// worker.js
import workerDepts from "../util/worker/worker-depts";
  1. 定义公共调用函数,引入所打包的依赖并串联流程:

worker 内定义一个公共调用函数,注入 worker-depts 依赖,并注册在 workerpool 的方法内

// worker.js
import workerDepts from "../util/worker/worker-depts";

function runWithDepts(fn: any, ...args: any) {
  var f = new Function("return (" + fn + ").apply(null, arguments);");
  return f.apply(f, [workerDepts].concat(args));
}

workerpool.worker({
  runWithDepts,
});

主线程文件内定义相应的调用方法,入参是自定义函数体和该函数的参数列表

// index.js
import workerpool from "workerpool";
export async function workerDraw(fn, ...args) {
  const pool = workerpool.pool("./worker.js");
  return pool.exec("runWithDepts", [String(fn)].concat(args));
}

完成以上步骤,就可以在项目任意需要调用 worker 的位置,像下面这样,自定义函数内容,引用所需依赖(已注入在函数第一个参数),进行使用了。

这里我们引用了一个项目内的公共函数 fibonacci,也引用了一个 lodashmap 方法,都可以在depts 对象上取到

// 项目内需使用worker时
const res = await workerDraw(
  (depts, m, n) => {
    const { map } = depts["_"];
    const { fibonacci } = depts["util/math"];
    return map([m, n], (num) => fibonacci(num));
  },
  input1,
  input2
);
  1. 优化语法支持

没有语法支持的依赖管理是很难用的,通过对 workerDraw 进行 ts 语法包装,可以实现在使用时的依赖提示:

import workerpool from "workerpool";
import type TDepts from "./worker-depts";

export async function workerDraw<T extends any[], R>(
  fn: (depts: typeof TDepts, ...args: T) => Promise<R> | R,
  ...args: T
) {
  const pool = workerpool.pool("./worker.js");
  return pool.exec("runWithDepts", [String(fn)].concat(args));
}

然后就可以在使用时获取依赖提示:

依赖示意

  1. 其他问题

新增了 worker 以后,出现了 windowworker 两种运行环境,如果你恰好和我一样需要兼容 node 端运行,那么运行环境就是三种,原本我们通常判断 window 环境使用的也许是 typeof window === ''object''这样,现在不够用了,这里可以改为 globalThis 对象,它是三套环境内都存在的一个对象,通过判断globalThis.constructor.name的值,值分别是''Window'' / ''DedicatedWorker''/ ''Object'',从而实现环境的区分

总结

通过使用 workerpool,添加依赖管理和构建公共 worker 调用函数,我们实现了一套按需调用,灵活强大的 worker 使用方式。

在京东羚珑的程序化设计项目中,通过把 skia 图形绘制部分逐步改造为 worker 内调用,我们实现了整体服务耗时降低 75% 的效果,收益还是非常不错的。

文中涉及的代码示例都已放在 github 上,内有 vitewebpack 两个完整实现版本,感兴趣的小伙伴可以 clone 下来参照着看~

参考资料

  • MDN Web Workers API
  • workerpool
  • 前端项目上 Web Worker 实践
  • Web Worker 文献综述

Ajax对Web套接字与Web Workers

Ajax对Web套接字与Web Workers

三者有什么区别?他们似乎做了同样的事情。为什么和什么时候选择使用一种方法?
AJAX和websockets执行类似的任务 – 它们都建立到服务器的通信通道。 Web工作人员与他们中的任何一个无关,它们只是JS执行的单独线程。

AJAX比websockets更成熟 – 它已经有了更长的时间,并且有更广泛的浏览器支持。 AJAX是面向请求的 – 您向服务器发出请求,服务器响应,并且连接已关闭。另一方面,Websockets建立与服务器的持久连接,您可以通过它连接两个方向的多个消息。

如果要在不阻止浏览器界面的情况下执行处理器密集型任务,Webworkers将非常有用。

ES6+Webpack 下使用 Web Worker

ES6+Webpack 下使用 Web Worker

大家都知道 HTML 5 新增了很多 API,其中就包括 Web Worker,在普通的 js 文件上使用 ES5 编写相关代码应该是完全没有问题了,只需要在支持 H5 的浏览器上就能跑起来。

那如果我们需要在 ES6+Webpack 的组合环境下使用 Web Worker呢?其实也很方便,只需要注意一下个别点,接下来记录一下我踩过的坑。

至于 Web Worker 的基础知识和基本 api 我就放到最后面当给还不了解或者没有系统使用过的读者们去简单阅读一下。

1. 快速创建工程环境

假设你已经有一份 ES6+Webpack 的代码工程环境,而且是可以顺利跑起来的;如果没有,可以 clone 我的 github 仓库:github.com/irm-github/…

2. 安装及使用 worker-loader

2.1 安装依赖:

$ npm install -D worker-loader
# 或
$ yarn add worker-loader --dev
复制代码

2.2 代码中直接使用 worker-loader

// main.js
var MyWorker = require("worker-loader!./file.js");
// var MyWorker = require("worker-loader?inline=true&fallback=false!./file.js"); var worker = new MyWorker(); worker.postMessage({a: 1}); worker.onmessage = function(event) { /* 操作 */ }; worker.addEventListener("message", function(event) { /* 操作 */ }); 复制代码

优点:写 worker 逻辑的脚本文件可以任意命名,只要传进 worker-loader 中处理即可; 缺点:每引入一次 worker 逻辑的脚本文件,就需要写一次如上所示的代码,需要多写 N(N>=1) 次的 "worker-loader!"

2.3 在 webpack 的配置文件中引入 worker-loader

{
  module: {
    rules: [
      {
        // 匹配 *.worker.js
        test: /\.worker\.js$/,
        use: { loader: ''worker-loader'', options: { name: ''[name]:[hash:8].js'', // inline: true, // fallback: false // publicPath: ''/scripts/workers/'' } } } ] } } 复制代码

其中配置,可以设置 inline 属性为 true 将 worker 作为 blob 进行内联; 要注意,内联模式将额外为浏览器创建 chunk,即使对于不支持内联 worker 的浏览器也是如此;若这种浏览器想要禁用这种行为,只需要将 fallback 参数设置为 false 即可。

3. 同源策略

Web Worker 严格遵守同源策略,如果 webpack 的静态资源与应用代码不是同源的,那么很有可能就被浏览器给墙掉了,而且这种场景也经常发生。对于 Web Worker 遇到这种情况,有两种解决方案。

3.1 第一种

通过设置 worker-loader 的选项参数 inline 把 worker 内联成 blob 数据格式,而不再是通过下载脚本文件的方式来使用 worker:

App.js

import Worker from ''./file.worker.js'';
复制代码

webpack.config.js

{
  loader: ''worker-loader''
  options: { inline: true }
}
复制代码

3.2 第二种

通过设置 worker-loader 的选项参数 publicPath 来重写掉 worker 脚本的下载 url,当然脚本也要存放到同样的位置:

App.js

// This will cause the worker to be downloaded from `/workers/file.worker.js`
import Worker from ''./file.worker.js'';
复制代码

webpack.config.js

{
  loader: ''worker-loader''
  options: { publicPath: ''/workers/'' }
}
复制代码

4. devServer 模式下报错 "window is not defined"

若使用了 webpack-dev-server 启动了本地调试服务器,则有可能会在控制台报错: "Uncaught ReferenceError: window is not defined"

反正我是遇到了,找了很久未果,当时还是洗了把脸冷静下来排查问题,尝试着先后在 worker-loaderwebpack-dev-serverwebpack 的 github 仓库的 issues 里面去找,最后果然在 webpack 的 github 仓库里找到了码友的提问,官方给出了答案:

只需要在 webpack 的配置文件下的 output 下,加一个属性对:globalObject: ''this''

output: {
  path: DIST_PATH,
  publicPath: ''/dist/'',
  filename: ''[name].bundle.[hash:8].js'', chunkFilename: "[name].chunk.[chunkhash:8].js", globalObject: ''this'', }, 复制代码

5. Web Worker 出现的背景

JavaScript 引擎是单线程运行的,JavaScript 中耗时的 I/O 操作都被处理为异步操作,它们包括键盘、鼠标 I/O 输入输出事件、窗口大小的 resize 事件、定时器(setTimeoutsetInterval)事件、Ajax 请求网络 I/O 回调等。当这些异步任务发生的时候,它们将会被放入浏览器的事件任务队列中去,等到 JavaScript 运行时执行线程空闲时候才会按照队列先进先出的原则被一一执行,但终究还是单线程。

平时看似够用的异步编程(promiseasync/await),在遇到很复杂的运算,比如说图像的识别优化或转换、H5游戏引擎的实现,加解密算法操作等等,它们的不足就将逐渐体现出来。长时间运行的 js 进程会导致浏览器冻结用户界面,降低用户体验。那有没有什么办法可以将复杂的计算从业务逻辑代码抽离出来,让计算运行的同时不阻塞用户操作界面获得反馈呢?

HTML5 标准通过了 Web Worker 的规范,该规范定义了一套 api,它允许一段 js 程序运行在主线程之外的另一个线程中。工作线程允许开发人员编写能够长时间运行而不被用户所中断的后台程序, 去执行事务或者逻辑,并同时保证页面对用户的及时响应,可以将一些大量计算的代码交给web worker运行而不冻结用户界面。

5. Web Worker 的类型

之前一直认为不就那一种类型吗,哪里还会有多类型的 Worker。答案是有的,其可分为两种类型:

  1. 专用 Worker, Dedicated Web Worker
  2. 共享 Worker, Shared Web Worker

「专用 Worker」只能被创建它的页面访问,而「共享 Worker」可以在浏览器的多个标签中打开的同一个页面间共享。

在 js 代码中,Woker 类代表 Dedicated WorkerShared Worker 类代表 Shared Web Worker

下面的一些示例代码我就直接用 ES5 去写了,上面教了大家怎么使用在 ES6+Webpack 的环境下,迁移这种工作大家就当练习,多动动手。

6. 如何创建 Worker

很简单

// 应用文件 app.js
var worker = new Worker(''./my.worker.js''); // 传入 worker 脚本文件的路径即可 复制代码

7. 如何与 worker 通信

就通过两个方法即可完成:

应用文件 app.js

// 创建 worker 实例
var worker = new Worker(''./my.worker.js''); // 传入 worker 脚本文件的路径即可 // 监听消息 worker.onmessage = function(evt){ // 主线程收到工作线程的消息 }; // 主线程向工作线程发送消息 worker.postMessage({ value: ''主线程向工作线程发送消息'' }); 复制代码

worker 文件 my.worker.js

// 监听消息
this.onmessage = function(evt){ // 工作线程收到主线程的消息 }; this.postMessage({ value: ''工作线程向主线程发送消息'' }); 复制代码

8. Worker 的全局作用域

使用 Web Worker 最重要的一点是要知道,它所执行的 js 代码完全在另一作用域中,与当前主线程的代码不共享作用域。在 Web Worker 中,同样有一个全局对象和其他对象以及方法,但其代码无法访问 DOM,也不能影响页面的外观。

Web Worker 中的全局对象是 worker 对象本身,也即 thisself 引用的都是 worker 对象,说白了,就像上一段在 my.worker.js 的代码,this 完全可以换成 self,甚至可以省略。

为便于处理数据,Web Worker 本身也是一个最小化的运行环境,其可以访问或使用如下数据:

  • 最小化的 navigator 对象 包括 onLine, appName, appVersion, userAgentplatform 属性
  • 只读的 location 对象
  • setTimeout(), setInterval(), clearTimeout(), clearInterval() 方法
  • XMLHttpRequest 构造函数

9. 如何终止工作线程

如果在某个时机不想要 Worker 继续运行了,那么我们需要终止掉这个线程,可以调用在主线程 Worker 的 terminate 方法 或者在相应的线程中调用 close 方法:

应用文件 app.js

var worker = new Worker(''./worker.js'');
// ...一些操作
worker.terminate();
复制代码

Worker 文件 my.worker.js

self.close();
复制代码

10. Worker 的错误处理机制

具体来说,Worker 内部的 js 在执行过程中只要遇到错误,就会触发 error 事件。发生 error 事件时,事件对象中包含三个属性:filename, linenomessage,分别表示发生错误的文件名、代码行号和完整的错误消息。

worker.addEventListener(''error'', function (e) {
  console.log(''MAIN: '', ''ERROR'', e); console.log(''filename:'' + e.filename + ''-message:'' + e.message + ''-lineno:'' + e.lineno); }); 复制代码

11. 引入脚本与库

Worker 线程能够访问一个全局函数 importScripts() 来引入脚本,该函数接受 0 个或者多个 URI 作为参数来引入资源;以下例子都是合法的:

importScripts();                        /* 什么都不引入 */
importScripts(''foo.js'');                /* 只引入 "foo.js" */
importScripts(''foo.js'', ''bar.js''); /* 引入两个脚本 */ 复制代码

浏览器加载并运行每一个列出的脚本。每个脚本中的全局对象都能够被 worker 使用。如果脚本无法加载,将抛出 NETWORK_ERROR 异常,接下来的代码也无法执行。而之前执行的代码(包括使用 window.setTimeout() 异步执行的代码)依然能够运行。importScripts() 之后的函数声明依然会被保留,因为它们始终会在其他代码之前运行。

注意: 脚本的下载顺序不固定,但执行时会按照传入 importScripts() 中的文件名顺序进行。这个过程是同步完成的;直到所有脚本都下载并运行完毕, importScripts() 才会返回。

 

HTML5 WebApp part4:使用 Web Workers 来加速您的移动 Web 应用程序(上) ...

HTML5 WebApp part4:使用 Web Workers 来加速您的移动 Web 应用程序(上) ...

       一直以来,Web 应用程序被局限在一个单线程世界中。这的确限制了开发人员在他们的代码中的作为,因为任何太复杂的东西都存在冻结应用程序 UI 的风险。通过将多线程引入 Web 应用程序,Web Workers 扭转了这一不利局面。这对于大部分应用程序逻辑都位于客户端的移动 Web 应用程序来说尤其有用。在本文中,您将了解如何使用 Web Workers 并发现哪些任务最适合它们。您还将看到如何使用其他 HTML 5 技术才能提高使用那些技术的效率。

       开始

       在本文中,您将使用最新的 Web 技术开发 Web 应用程序。这里的大部分代码只是 HTML、JavaScript 和 CSS — 所有 Web 开发人员的核心技术。所需的最重要的工具是用于进行测试的浏览器。本文大部分代码将在最新桌面浏览器上运行,但也有一些例外,我们将在文章中进行说明。当 然,您也必须在移动浏览器上测试,为此,您需要最新的 iPhone 和 Android SDKs。本文将使用 iPhone SDK 3.1.3 和 Android SDK 2.1。本文的样例还将使用一个代理服务器来从浏览器访问远程服务。这个代理服务器是一个简单的 Java™ servlet,但也可以使用以 PHP、Ruby 以及其他语言编写的代理轻松替换。获取链接。

       移动设备上的多线程 JavaScript

       对于大多数开发人员来说,多线程或并发编程并不新鲜。但是,JavaScript 并不是一种支持并发编程的语言。JavaScript 的创建者认为,对于 JavaScript 这样旨在 Web 页面上执行简单任务的语言来说,并发编程容易出现问题,而且没有必要。然而,由于 Web 页面已经发展成为 Web 应用程序,使用 JavaScript 完成的任务的复杂程度已经大大增加,向 JavaScript 提出了与其他语言同等的要求。与此同时,使用其他支持并发编程的语言工作的开发人员经常面临伴随线程和 mutexes 这样的并发原语而来的超高复杂性的困扰。实际上,最近像 Scala、Clojure 和 F# 这样的几种新语言已经发展,它们都有可能简化并发性。

       Web Worker 规范不只是向 JavaScript 和 Web 浏览器添加并发性,而且是以一种智慧的方式添加,这种方式将增加开发人员的能力,但不会向他们提供一种会导致问题的工具。 例如,多年来,桌面应用程序开发人员一直在使用多线程来支持他们的应用程序访问多个 I/O 资源,以避免在等待这些资源时冻结 UI。然而,当这些多线程更改共享的资源(包括 UI)时,这样的应用程序通常会出现问题,因为这种行为可能会导致应用程序冻结或崩溃。有了 Web Workers,这种情况就不会发生。衍生线程不能访问主 UI 线程访问的资源。事实上,衍生线程中的代码甚至不能与主 UI 线程执行的代码位于同一个文件中。

       您甚至必须提供相应的外部文件作为构造函数的一部分,如 清单 1 所示。

       这个进程使用三个资源:

  1. 在主线程上执行的 Web 页面 JavaScript(我称其为页面脚本)。
  2. Worker 对象,这是用于执行 Web Worker 函数的 JavaScript 对象。
  3. 将在新衍生的线程上执行的脚本。我称其为 Worker 脚本

      让我们首先看看 清单 1 中的页面脚本。

立即学习“前端免费学习笔记(深入)”;

清单 1.在页面脚本中使用一个 Web Worker

var worker = new Worker("worker.js");
worker.onmessage = function(message){
    // do stuff
};
worker.postMessage(someDataToDoStuffWith);
登录后复制

在 清单 1 中,您可以看到使用 Web Workers 的三个基本步骤。首先,您创建一个 Worker 对象并向它传递将在新线程中执行的脚本的 URL。Worker 将执行的所有代码都必须包含在一个 Worker 脚本中,该脚本的 URL 将被传递到这个 Worker 的构造函数中。这个 Worker 脚本的 URL 受到浏览器的同源策略的限制 — 它必须来自加载这个页面的同一个域,该页面已加载正在创建这个 Web Worker 的页面脚本。

下一步是使用 onmessage 函数指定一个回调处理器函数。这个回调函数将在该 Worker 脚本执行后调用。message 是从该 Worker 脚本返回的数据,您可以随意处理该消息。回调函数在主线程上执行,因此它能访问 DOM。Worker 脚本在一个不同的线程内运行且不能访问 DOM,因此,您需要将来自这个 Worker 脚本的数据返回主线程,在那里,您可以安全地修改 DOM 来更新您的应用程序的 UI。这是 Web Workers 的无共享设计的关键特性。

清单 1 中的最后一行展示如何通过调用 Worker 的 postMessage 函数来启动它。这里,您传递一条消息(重申一下,它只是数据)给 Worker。当然,postMessage 是一个异步函数;您调用它,它就立即返回。

现在,检查这个 Worker 脚本。清单 2 中的代码是来自 清单 1 的 worker.js 文件的内容。

清单 2. 一个 Worker 脚本

importScripts("utils.js");
var workerState = {};
onmessage = function(message){
     workerState = message.data;
      // do stuff with the message
    postMessage({responseXml: this.responseText});
}
登录后复制

可以看到,这个 Worker 脚本拥有自己的 onmessage 函数。该函数在您从主线程调用 postMessage 时调用。从页面脚本传来的数据被传递到 message 对象中的 postMessage 函数。您通过检索 message 对象的 data 属性来访问该数据。当您处理完 Worker 脚本中的数据时,调用 postMessage 函数将数据返回主线程。主线程也可以通过访问它接收到的消息的 data 属性来访问该数据。

至此,您已经见识了 Web Workers 的这个简单、但强大的语义。接下来,您将了解如何应用这个语义来加速移动 Web 应用程序。在此之前,有必要先讨论一下设备支持。毕竟,这些是移动 Web 应用程序,且处理不同浏览器之间的功能的区别对于移动 Web 应用程序开发很重要。

设备支持

从 Android 2.0 开始,Android 浏览器就拥有了对 HTML 5 Web Worker 规范的全面支持。在撰写本文之时,最新的 Android 设备(包括非常流行的 Motorola Droid)已配置了 Android 2.1。另外,此特性在运行 Maemo 操作系统的 Nokia 设备上的 Mozilla Fennec 浏览器以及 Windows Mobile 设备上受到完全支持。这里需要引起注意的遗漏是 iPhone。iPhone OS 3.1.3 和 3.2 版(在 iPad 上运行的 OS 的版本)并不支持 Web Workers。但是,此特性已在 Safari 上受到支持,因此,此特性在运行在 iPhone 上的 Mobile Safari 浏览器上出现应该只是一个时间问题。鉴于 iPhone 的主导地位(尤其是在美国),最好不要依赖 Web Workers 的存在,且不要只在您检测到它们的存在时才使用它们来增强您的移动 Web 应用程序。意识到这一点后,我们来看看如何使用 Web Workers 来加速您的移动 Web 应用程序。

 

使用 Workers 改善性能

智能手机浏览器上的 Web Worker 支持很不错,而且一直在不断改进。这就提出了一个问题:什么时候需要在移动 Web 应用程序中使用 Workers?答案很简单:需要完成耗时的任务的任何时候。有些示例展示了如何将 Workers 用于执行密集的数学计算,比如计算 1 万位数的圆周率。很可能您永远也不需要在 Web 应用程序上执行这样一个计算,在移动 Web 应用程序上执行这种计算的几率则更小。但是,从远程资源检索数据则相当常见,这也是本文示例的关注点。

在这个示例中,您将从 eBay 检索一个 Daily Deals(每天都在变化的交易)列表。这个交易列表包含关于每笔交易的简短信息。更详细的信息可以通过使用 eBay 的 Shopping API 获取。当用户浏览这个交易列表选择感兴趣的商品时,您将使用 Web Workers 来预取这个附加信息。要从您的 Web 应用程序访问所有这些 eBay 数据,您需要通过使用一个泛型代理(generic proxy)来处理浏览器的同源策略。一个简单的 Java servlet 将用于这个代理,它包含在本文的代码中,但不在这里单独展示。相反,我们将把注意力集中在处理 Web Workers 的代码上。清单 3 展示了这个交易应用程序的基本 HTML 页面。

清单 3. 交易应用程序 HTML

  
    <meta http-equiv="content-type" content="text/html; charset=UTF-8"><meta name="viewport" content="width = device-width"><title>Worker Deals</title><script type="text/javascript" src="common.js"></script><h1>Deals</h1>
    <ol id="deals"></ol><h2>More Deals</h2>
    
登录后复制

    可以看出,这是一段非常简单的 HTML;它只是一个 shell。您使用 JavaScript 检索数据并生成 UI。这是移动 Web 应用程序的优化设计,因为它允许将所有代码和静态标记缓存到设备上,用户只需等待来自服务器的数据。注意,在 清单 3 中,一旦那个 body 加载,您就调用 loadDeals 函数,在那里,您将加载 清单 4 中的应用程序的初始数据。

    今天关于web worker 实践web实践总结的讲解已经结束,谢谢您的阅读,如果想了解更多关于2023 年的 Web Worker 项目实践、Ajax对Web套接字与Web Workers、ES6+Webpack 下使用 Web Worker、HTML5 WebApp part4:使用 Web Workers 来加速您的移动 Web 应用程序(上) ...的相关知识,请在本站搜索。

    本文标签: