🎉🎉🎉 Web Workers 使用秘籍,祝您早日通关前端多线程!

简介: Web Workers 是新一代的异步编程解决方案,它可以让我们在后台运行一个脚本,而不会阻塞用户界面。对于前端开发者来说,Web Workers 是一个非常有用的工具,它可以让我们在后台运行一些

Web Workers 是新一代的异步编程解决方案,它可以让我们在后台运行一个脚本,而不会阻塞用户界面。

对于前端开发者来说,Web Workers 是一个非常有用的工具,它可以让我们在后台运行一些耗时的任务,比如计算、数据处理等,而不会阻塞用户界面。

接下来就带你正式上手 Web Workers。

开始之前的准备工作

根据评论区的小伙伴的需求,特地补上这一说明。

Web Workers是需要运行在服务环境中(http/https协议),也就是如果我们通过本地直接预览html是不行的(file协议),这个时候解决方案有很多,最简单的解决方案是通过 ide,下面就介绍各种解决方案。

WebStorm 用户

我就是WebStorm的用户,可以直接在html文件中右键点击,然后选择运行 or 调试都可以。

image.png

vscode 用户

vscode可以在vscode中安装Live Server插件;

安装成功后,用vscode打开html文件所在的文件夹

vscode中直接右击 Open with Live Server打开即可!

不想装插件?

不想装插件就麻烦一些了:

  1. 可以直接下载tomcat或者nginx在自己的电脑上面跑一个服务。
  2. 可以通过http-server来开启一个服务,npm install http-server -g
  3. 使用node来搭建一个服务环境,node有很多插件包可以达到这个效果。

方法有很多,开阔思路最重要。

1. 什么是 Web Workers

Web Workers 是一个新的JavaScript API,它可以让我们在后台运行一个脚本,而不会阻塞用户界面。

它是独立于主线程的一个线程,当然它为了不阻塞主线程,也有一些限制,比如不能访问DOM,也不能访问其他脚本创建的变量。

因为有上面的限制,所以Web Workers不想多线程编程语言一样,有锁的概念,也不会有线程安全的问题。

它的使用方式非常简单,只需要创建一个Worker对象,然后调用它的postMessage方法,就可以在后台运行一个脚本了。

现在我们来看一个简单的例子:

  • main.js
// main.js
// 创建一个 Worker 对象
const worker = new Worker('worker.js');

// 调用 postMessage 方法,传递一个消息
worker.postMessage('Hello World!');
  • worker.js
// worker.js
// 监听消息
self.addEventListener('message', (event) => {
   
   
    console.log(event.data);
});

在上面的例子中,我们在main.js中创建了一个Worker对象,然后调用它的postMessage方法,传递了一个消息。

worker.js中,我们监听了message事件,当main.js中的Worker对象调用postMessage方法时,就会触发message
事件,我们就可以在事件回调中获取到传递过来的消息。

注意:worker.js中的self指向的是WorkerGlobalScope对象,它是Worker对象的全局作用域,它的addEventListener方法用来监听事件。

2. 传递数据

上面的示例中,我们只是传递了一个字符串,但是实际上,我们可以传递任何数据类型,比如ArrayBufferBlobMessagePort等。

我们来看一个例子:

  • main.js
// main.js
const worker = new Worker('worker.js');

// 创建一个 ArrayBuffer 对象
const buffer = new ArrayBuffer(16);

// 创建一个 Int32Array 对象
const int32View = new Int32Array(buffer);

// 设置 Int32Array 对象的值
for (let i = 0; i < int32View.length; i++) {
   
   
    int32View[i] = i * 2;
}

// 传递一个 ArrayBuffer 对象
worker.postMessage(buffer);
// 传递一个 Int32Array 对象
worker.postMessage(int32View);
  • worker.js
// worker.js
self.addEventListener('message', (event) => {
   
   
    // 获取 ArrayBuffer 对象
    const buffer = event.data;

    // 创建一个 Int32Array 对象
    const int32View = new Int32Array(buffer);

    // 打印 Int32Array 对象的值
    for (let i = 0; i < int32View.length; i++) {
   
   
        console.log(int32View[i]);
    }
});

在上面的例子中,我们在main.js中创建了一个ArrayBuffer对象,然后创建了一个Int32Array
对象,最后把这两个对象都传递给了Worker对象。

worker.js中,我们监听了message事件,然后获取到了传递过来的对象,然后创建了一个Int32Array对象,最后打印了这个对象的值。

这里有一个问题就是我们如何知道传递过来的是ArrayBuffer对象还是Int32Array对象呢?

这里有很多种方法可以判断,比如我们可以在传递的时候,把对象的类型也传递过去,或者我们可以在传递的时候,把对象的类型作为key
,对象作为value,然后在worker.js中,通过key来获取到对象。

这里我只是引出一个问题,就是web worker中,我们只有一个message事件,同时我们可以传递任何JavaScript
对象,所以我们可以根据自己的需求,来定义传递的数据格式。

例如可以定义一个对象,然后把对象的类型作为key,对象作为value,然后在worker.js中,通过key来获取到对象。

// main.js
const worker = new Worker('worker.js');

// 创建一个 ArrayBuffer 对象
const buffer = new ArrayBuffer(16);

// 创建一个 Int32Array 对象
const int32View = new Int32Array(buffer);

// 传递一个 ArrayBuffer 对象
worker.postMessage({
   
   
    type: 'ArrayBuffer',
    data: buffer
});

// 传递一个 Int32Array 对象
worker.postMessage({
   
   
    type: 'Int32Array',
    data: int32View
});

这里就说这么多了,接下来我们来看一下web worker是怎么把数据传递给主线程的。

3. 传递数据给主线程

web worker中,我们可以通过postMessage方法来向主线程传递数据,这个方法的参数可以是任何JavaScript对象,比如String
NumberBooleanArrayObject等。

是的worker中同样也有postMessage方法,用于向主线程传递数据。

// worker.js
self.addEventListener('message', (event) => {
   
   
    // 向主线程传递数据
    self.postMessage('收到了!!!');
});

在上面的例子中,我们在worker.js中监听了message事件,然后在事件处理函数中,向主线程传递了一个String对象。

在主线程中,我们可以通过Worker对象的onmessage属性来监听worker传递过来的数据。

// main.js
const worker = new Worker('worker.js');

worker.onmessage = (event) => {
   
   
    console.log(event.data);
};

在上面的例子中,我们在主线程中创建了一个Worker对象,然后监听了worker传递过来的数据。

是不是很简单,主线程通过postMessage方法向worker传递数据,worker也是通过postMessage方法向主线程传递数据。

不同的是主线程通过onmessage属性来监听worker传递过来的数据,而worker通过addEventListener方法来监听主线程传递过来的数据。

4. 异常处理

web worker中,如果遇到了异常,它是不会抛出异常的,而是会触发error事件。

也不是不会抛出异常,而是抛出的异常不是在主线程中,所以对于主线程来说是无感的,但是我们需要知道这个异常,于是就有了error事件。

// worker.js
self.addEventListener('message', (event) => {
   
   
    // 抛出异常
    throw new Error('出错了!!!');
});

self.addEventListener('error', (event) => {
   
   
    console.log(event.message);
});

上面是在worker.js中抛出异常的例子,我们在worker.js中监听了message
事件,然后在事件处理函数中抛出了一个异常,然后在worker.js中监听了error事件,当worker抛出异常时,就会触发error事件。

在主线程中,我们可以通过Worker对象的onerror属性来监听worker抛出的异常。

// main.js
const worker = new Worker('worker.js');

worker.onerror = (event) => {
   
   
    console.log(event.message);
};

在上面的例子中,我们在主线程中创建了一个Worker对象,然后监听了worker抛出的异常。

messageerror 事件

除了上面的message事件和error事件之外,web worker还有一个messageerror事件,同样它也同时存在于主线程和worker中。

它的作用是当传递的数据无法被序列化,那么就会触发messageerror事件。

注意了,它和error事件不一样,error事件是当worker抛出异常时触发的,而messageerror事件是当传递的数据无法被序列化时触发的。

  • worker.js
// worker.js
self.addEventListener('message', (event) => {
   
   
    // 向主线程传递数据
    self.postMessage('收到了!!!');
});

self.addEventListener('messageerror', (event) => {
   
   
    console.log(event.message);
});
  • main.js
// main.js
const worker = new Worker('worker.js');

worker.postMessage({
   
   
    func: () => {
   
   
    }
})

worker.onmessageerror = (event) => {
   
   
    console.log(event.message);
};

上面的例子中主线程向worker传递了一个对象,但是对象中有一个函数,函数是无法被序列化的,所以会触发messageerror事件。

上面只会触发主线程的messageerror事件,但是不会触发error事件。

worker中的messageerror事件和主线程中的messageerror事件也是同理,worker
如果传递了无法被序列化的数据,那么就会触发workermessageerror事件。

5. 关闭worker

关闭web worker指的是关闭worker线程,就简简单单的停止worker线程的运行,让worker线程不会有任何反应机会。

关闭了的worker是无法再次启动的,如果想要再次启动,那么就需要重新创建一个worker,没有起死回生的机会。

web worker中,我们可以通过close方法来关闭worker

// worker.js
self.addEventListener('message', (event) => {
   
   
    // 关闭worker
    self.close();
});

在上面的例子中,我们在worker.js中监听了message事件,然后在事件处理函数中关闭了worker

在主线程中,我们可以通过Worker对象的terminate方法来关闭worker

// main.js
const worker = new Worker('worker.js');

worker.terminate();

在上面的例子中,我们在主线程中创建了一个Worker对象,然后调用了terminate方法来关闭worker

6. worker线程限制

在文章开头我们提到了,web worker是运行在另一个线程中的,这个线程是独立于主线程的,它无法操作主线程的DOM

除了这个限制之外,看上面的描述,它是独立于主线程的,所以它无法访问主线程的任何东西,包括全局变量。

就是因为有了这么些限制,所以web worker才能够在不影响主线程的情况下运行,也就是说web worker
是线程安全的,不像其他的多线程编程,还需要考虑线程安全的问题。

7. worker的实用场景

web worker的出现,然后我们拥有了一个可以发挥多线程能力的工具,那么它有什么实用的场景呢?

很多时候我们会遇到一些耗时的操作,比如说一些复杂的计算,或者是一些网络请求,这些操作都会阻塞主线程,导致页面卡顿,用户体验不好。

这个时候我们就可以把这些耗时的操作放到worker中去执行,这样就不会阻塞主线程了,用户体验会好很多。

就拿网上传烂了的例子,前端一次性渲染十万条数据来说,网上的示例优化的都是DOM
的渲染,但是这个优化对于数据的处理是没有任何帮助的,因为数据的处理是在主线程中执行的,所以还是会阻塞主线程。

例如你有十万条数据,用户怎么可能看的完?肯定是需要有查询筛选的功能,可想而知这个筛选的过程是有多么的耗时,如果是在主线程中执行,那么势必会阻塞主线程,导致页面卡顿。

这个时候我们就可以把数据的处理放到worker中去执行,这样就不会阻塞主线程了,用户体验会好很多。

看示例:

  • main.js
// main.js
const worker = new Worker('worker.js');

const params = {
   
   
    name: '',
    age: ''
}
worker.postMessage({
   
   search: params});

worker.onmessage = (event) => {
   
   
    renderData(event.data);
};

const renderData = (data) => {
   
   
    // 渲染数据,这里就是网上说的虚拟滚动的实现
};
  • worker.js
// worker.js
const loadData = () => {
   
   
    // 加载数据
    ajax({
   
   
        url: 'http://xxx.com',
        success: (data) => {
   
   
            self.postMessage(data);
        }
    });
};

const getData = (search) => {
   
   
    // 处理数据,肯定是需要循环 10w 次的
    for (let i = 0; i < 100000; i++) {
   
   
        // 这里就是处理数据的逻辑
    }
};

self.addEventListener('onmessage', (event) => {
   
   
    const {
   
   search} = event.data;
    const data = getData(search);
    self.postMessage(data);
});

上面就是一个优化的案例,可以将worker中的代码放到主线程中,对比一下效果,同时也建议大家可以自己写一个简单的例子,体验一下。

8. 总结

总体来说web worker还是比较简单的,上面介绍Worker对象:

Worker对象,只有一个构造函数,两个方法,三个监听事件:

  1. 一个构造函数:Worker()
    • 用来创建一个worker对象
  2. 两个方法:
    • postMessage():用来向worker发送消息
    • terminate():用来终止worker线程
  3. 三个监听事件:
    • onmessage:用来监听worker发送的消息
    • onerror:用来监听worker线程的错误
    • onmessageerror:用来监听worker发送的消息的错误

Worker对象文件中,自带一个slef对象,可以用来监听主线程发送的消息,也可以用来向主线程发送消息:

  1. self.addEventListener([eventName], (event) => {}):用来监听主线程发送的消息
    • eventName:监听的事件名称
      • message:用来监听主线程发送的消息
      • error:用来监听主线程发送的错误
      • messageerror:用来监听主线程发送的消息的错误
    • event:事件对象
      • data:主线程发送的数据
  2. self.postMessage():用来向主线程发送消息
  3. self.close():用来关闭worker线程

真香预告:

  1. Worker中还可以创建多个Worker,打开多线程编程的大门。
  2. ServiceWorker让你的网页拥抱服务端的能力。
  3. SharedWorker让你多个页面相互通信。
  4. 点个赞才有后面的...(我不是骗赞,是后面的内容一下没想好)
目录
相关文章
|
缓存 前端开发 JavaScript
"面试通关秘籍:深度解析浏览器面试必考问题,从重绘回流到事件委托,让你一举拿下前端 Offer!"
【10月更文挑战第23天】在前端开发面试中,浏览器相关知识是必考内容。本文总结了四个常见问题:浏览器渲染机制、重绘与回流、性能优化及事件委托。通过具体示例和对比分析,帮助求职者更好地理解和准备面试。掌握这些知识点,有助于提升面试表现和实际工作能力。
254 1
|
移动开发 JavaScript 前端开发
HTML5 Web Workers详解
HTML5 Web Workers 允许在后台线程中运行 JavaScript,实现复杂计算而不影响用户界面,提升应用性能。其主要特性包括并行处理、异步通信、独立作用域及多数据类型支持。通过创建和使用 Worker 文件,如 `worker.js`,可执行后台任务,并与主线程通过消息传递机制通信。适用于数据处理、图像处理、复杂计算及网络请求并行等场景。需要注意的是,Web Workers 在浏览器兼容性、安全性限制、调试及资源消耗方面需特别关注。合理利用 Web Workers 可显著增强 Web 应用的流畅度和响应速度。
|
SQL 安全 数据库
Web安全漏洞专项靶场—SQL注入—docker环境—sqli-labs靶场—详细通关指南
Web安全漏洞专项靶场—SQL注入—docker环境—sqli-labs靶场—详细通关指南
1670 1
|
JavaScript 前端开发 安全
轻松上手Web Worker:多线程解决方案的使用方法与实战指南
轻松上手Web Worker:多线程解决方案的使用方法与实战指南
416 0
|
前端开发 JavaScript 大数据
React与Web Workers:开启前端多线程时代的钥匙——深入探索计算密集型任务的优化策略与最佳实践
【8月更文挑战第31天】随着Web应用复杂性的提升,单线程JavaScript已难以胜任高计算量任务。Web Workers通过多线程编程解决了这一问题,使耗时任务独立运行而不阻塞主线程。结合React的组件化与虚拟DOM优势,可将大数据处理等任务交由Web Workers完成,确保UI流畅。最佳实践包括定义清晰接口、加强错误处理及合理评估任务特性。这一结合不仅提升了用户体验,更为前端开发带来多线程时代的全新可能。
425 1
|
缓存 JavaScript 前端开发
Web Workers与Service Workers:后台处理与离线缓存
Web Workers 和 Service Workers 是两种在Web开发中处理后台任务和离线缓存的重要技术。它们在工作原理和用途上有显著区别。
260 1
|
移动开发 数据挖掘 API
HTML5 中 Web Workers API 的用法
【8月更文挑战第24天】
175 0
|
网络协议 安全 Python
我们将使用Python的内置库`http.server`来创建一个简单的Web服务器。虽然这个示例相对简单,但我们可以围绕它展开许多讨论,包括HTTP协议、网络编程、异常处理、多线程等。
我们将使用Python的内置库`http.server`来创建一个简单的Web服务器。虽然这个示例相对简单,但我们可以围绕它展开许多讨论,包括HTTP协议、网络编程、异常处理、多线程等。
|
移动开发 前端开发 JavaScript
Web 前端开发精华文章推荐(HTML5、CSS3、jQuery)【系列二十二】
  《Web 前端开发精华文章推荐》2014年第一期(总第二十二期)和大家见面了。梦想天空博客关注 前端开发 技术,分享各类能够提升网站用户体验的优秀 jQuery 插件,展示前沿的 HTML5 和 CSS3 技术应用,推荐优秀的 网页设计 案例,共享精美的设计素材和优秀的 Web 开发工具,希望这些精心整理的前端技术文章能够帮助到您。
7723 0
|
2月前
|
算法 Java Go
【GoGin】(1)上手Go Gin 基于Go语言开发的Web框架,本文介绍了各种路由的配置信息;包含各场景下请求参数的基本传入接收
gin 框架中采用的路优酷是基于httprouter做的是一个高性能的 HTTP 请求路由器,适用于 Go 语言。它的设计目标是提供高效的路由匹配和低内存占用,特别适合需要高性能和简单路由的应用场景。
250 4