🎉🎉🎉 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. 点个赞才有后面的...(我不是骗赞,是后面的内容一下没想好)
目录
相关文章
|
28天前
|
前端开发 JavaScript 安全
前端性能调优:HTTP/2与HTTPS在Web加速中的应用
【10月更文挑战第27天】本文介绍了HTTP/2和HTTPS在前端性能调优中的应用。通过多路复用、服务器推送和头部压缩等特性,HTTP/2显著提升了Web性能。同时,HTTPS确保了数据传输的安全性。文章提供了示例代码,展示了如何使用Node.js创建一个HTTP/2服务器。
44 3
|
14天前
|
消息中间件 前端开发 JavaScript
探索微前端架构:构建现代Web应用的新策略
本文探讨了微前端架构的概念、优势及实施策略,旨在解决传统单体应用难以快速迭代和团队协作的问题。微前端允许不同团队独立开发、部署应用的各部分,提升灵活性与可维护性。文中还讨论了技术栈灵活性、独立部署、团队自治等优势,并提出了定义清晰接口、使用Web组件、状态管理和样式隔离等实施策略。
|
25天前
|
监控 前端开发 JavaScript
探索微前端架构:构建可扩展的现代Web应用
【10月更文挑战第29天】本文探讨了微前端架构的核心概念、优势及实施策略,通过将大型前端应用拆分为多个独立的微应用,提高开发效率、增强可维护性,并支持灵活的技术选型。实际案例包括Spotify和Zalando的成功应用。
|
29天前
|
前端开发 安全 应用服务中间件
前端性能调优:HTTP/2与HTTPS在Web加速中的应用
【10月更文挑战第26天】随着互联网的快速发展,前端性能调优成为开发者的重要任务。本文探讨了HTTP/2与HTTPS在前端性能优化中的应用,介绍了二进制分帧、多路复用和服务器推送等特性,并通过Nginx配置示例展示了如何启用HTTP/2和HTTPS,以提升Web应用的性能和安全性。
27 3
|
29天前
|
前端开发 JavaScript API
前端框架新探索:Svelte在构建高性能Web应用中的优势
【10月更文挑战第26天】近年来,前端技术飞速发展,Svelte凭借独特的编译时优化和简洁的API设计,成为构建高性能Web应用的优选。本文介绍Svelte的特点和优势,包括编译而非虚拟DOM、组件化开发、状态管理及响应式更新机制,并通过示例代码展示其使用方法。
43 2
|
1月前
|
缓存 前端开发 JavaScript
"面试通关秘籍:深度解析浏览器面试必考问题,从重绘回流到事件委托,让你一举拿下前端 Offer!"
【10月更文挑战第23天】在前端开发面试中,浏览器相关知识是必考内容。本文总结了四个常见问题:浏览器渲染机制、重绘与回流、性能优化及事件委托。通过具体示例和对比分析,帮助求职者更好地理解和准备面试。掌握这些知识点,有助于提升面试表现和实际工作能力。
64 1
|
2月前
|
人工智能 前端开发
2024 川渝 Web 前端开发技术交流会「互联」:等你来报名!
2024 川渝 Web 前端开发技术交流会「互联」:等你来报名!
2024 川渝 Web 前端开发技术交流会「互联」:等你来报名!
|
1月前
|
监控 前端开发 JavaScript
前端技术探索:构建高效、可维护的Web应用
【10月更文挑战第23天】前端技术探索:构建高效、可维护的Web应用
44 0
|
2月前
|
存储 人工智能 前端开发
前端大模型应用笔记(三):Vue3+Antdv+transformers+本地模型实现浏览器端侧增强搜索
本文介绍了一个纯前端实现的增强列表搜索应用,通过使用Transformer模型,实现了更智能的搜索功能,如使用“番茄”可以搜索到“西红柿”。项目基于Vue3和Ant Design Vue,使用了Xenova的bge-base-zh-v1.5模型。文章详细介绍了从环境搭建、数据准备到具体实现的全过程,并展示了实际效果和待改进点。
148 2
|
2月前
|
JavaScript 前端开发 程序员
前端学习笔记——node.js
前端学习笔记——node.js
44 0