【JavaScript 漫游】【044】Web Worker

2024-03-27 04:52
文章标签 java script web worker 漫游 044

本文主要是介绍【JavaScript 漫游】【044】Web Worker,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

夏日街道

文章简介

本篇文章为【JavaScript 漫游】专栏的第 044 篇文章,对浏览器模型的 Web Worker 相关知识点进行了总结。

概述

JavaScript 语言采用的是单线程模型,也就是说,所有任务只能在一个线程上完成,一次只能做一件事。前面的任务没做完,后面的任务只能等着。随着电脑计算能力的增强,尤其是多核 CPU 的出现,单线程带来很大的不便,无法充分发挥计算机的计算能力。

Web Worker 的作用,就是为 JavaScript 创造多线程环境,允许主线程创建 Worker 线程,将一些任务分配给后者运行。在主线程运行的同时,Worker 线程在后台运行,两者互不干扰。等到 Worker 线程完成计算任务,再把结果返回给主线程。这样的好处是,一些计算密集型或高延迟的任务可以交由 Worker 线程执行,主线程(通常负责 UI 交互)能够保持流畅,不会被阻塞或拖慢。

Worker 线程一旦新建成功,就会始终运行,不会被主线程上的活动(比如用户点击按钮、提交表单)打断。这样有利于随时响应主线程的通信。但是,这也造成了 Worker 比较耗费资源,不应该过度使用,而且一旦使用完毕,就应该关闭。

Web Worker 有以下几个使用注意点。

同源限制

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

DOM 限制

Worker 线程所在的全局对象,与主线程不一样,无法读取主线程所在网页的 DOM 对象,也无法使用 documentwindowparent 这些对象。但是,Worker 线程可以使用 navigator 对象和 location 对象。

全局对象限制

Worker 的全局对象 WorkerGlobalScope,不同于网页的全局对象 Window,很多接口拿不到。比如,理论上 Worker 线程不能使用 console.log,因为标准里面没有提到 Worker 的全局对象存在 console 接口,只定义了 Navigator 接口和 Location 接口。不过,浏览器实际上支持 Worker 线程使用 console.log,保险的做法还是不使用这个方法。

通信联系

Worker 线程和主线程不在同一个上下文环境,它们不能直接通信,必须通过消息完成。

脚本限制

Worker 线程不能执行 alert() 方法和 confirm() 方法,但可以使用 XMLHttpRequest 对象发出 AJAX 请求。

文件限制

Worker 线程无法读取本地文件,即不能打开本机的文件系统( file://),它所加载的脚本,必须来自网络。

基本用法

主线程

主线程采用 new 命令,调用 Worker() 构造函数,新建一个 Worker 线程。

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

Worker() 构造函数的参数是一个脚本文件,该文件就是 Worker 线程所要执行的任务。由于 Worker 不能读取本地文件,所以这个脚本必须来自网络。如果下载没有成功(比如404错误),Worker 就会默默地失败。

然后,主线程调用 worker.postMessage() 方法,向 Worker 发消息。

worker.postMessage('Hello World');
worker.postMessage({method: 'echo', args: ['Work']});

worker.postMessage() 的参数,就是主线程传给 Worker 的数据。它可以是各种数据类型,包括二进制数据。

接着,主线程通过 worker.onmessage 指定监听函数,接收子线程发回来的消息。

worker.onmessage = function (event) {doSomething(event.data);
}function doSomething() {// 执行任务worker.postMessage('Work done!');
}

上面代码中,事件对象的 data 属性可以获取 Worker 发来的数据。

Worker 完成任务以后,主线程就可以把它关掉。

worker.terminate();

Worker 线程

Worker 线程内部需要有一个监听函数,监听message事件。

self.addEventListener('message', function (e) {self.postMessage('You said: ' + e.data);
}, false);

上面代码中,self 代表子线程自身,即子线程的全局对象。因此,等同于下面两种写法。

// 写法一
this.addEventListener('message', function (e) {this.postMessage('You said: ' + e.data);
}, false);// 写法二
addEventListener('message', function (e) {postMessage('You said: ' + e.data);
}, false);

除了使用 self.addEventListener() 指定监听函数,也可以使用 self.onmessage 指定。监听函数的参数是一个事件对象,它的data 属性包含主线程发来的数据。self.postMessage() 方法用来向主线程发送消息。

根据主线程发来的数据,Worker 线程可以调用不同的方法。

self.addEventListener('message', function (e) {var data = e.data;switch (data.cmd) {case 'start':self.postMessage('WORKER STARTED: ' + data.msg);break;case 'stop':self.postMessage('WORKER STOPPED: ' + data.msg);self.close(); // Terminates the worker.break;default:self.postMessage('Unknown command: ' + data.msg);};
}, false);

Worker 加载脚本

Worker 内部如果要加载其他脚本,有一个专门的方法 importScripts()

importScripts('script1.js');

该方法可以同时加载多个脚本。

importScripts('script1.js', 'script2.js');

错误处理

主线程可以监听 Worker 是否发生错误。如果发生错误,Worker 会触发主线程的 error 事件。

worker.onerror(function (event) {console.log(['ERROR: Line ', event.lineno, ' in ', event.filename, ': ', event.message].join(''));
});// 或者
worker.addEventListener('error', function (event) {// ...
});

Worker 内部也可以监听 error 事件。

关闭 Worker

使用完毕,为了节省系统资源,必须关闭 Worker。

// 主线程
worker.terminate();// Worker 线程
self.close();

数据通信

主线程与 Worker 之间的通信内容,可以是文本,也可以是对象。需要注意的是,这种通信时拷贝关系,即是传值而不是传址,Worker 对通信内容的修改,不会影响到主线程。事实上,浏览器内部的运行机制是,先将通信内容串行化,然后把串行化后的字符串发给 Worker,后者再将它还原。

主线程与 Worker 之间也可以交换二进制数据,比如 File、Blob、ArrayBuffer 等类型,也可以在线程之间发送。

// 主线程
var uInt8Array = new Uint8Array(new ArrayBuffer(10));
for (var i = 0; i < uInt8Array.length; ++i) {uInt8Array[i] = i * 2; // [0, 2, 4, 6, 8,...]
}
worker.postMessage(uInt8Array);// Worker 线程
self.onmessage = function (e) {var uInt8Array = e.data;postMessage('Inside worker.js: uInt8Array.toString() = ' + uInt8Array.toString());postMessage('Inside worker.js: uInt8Array.byteLength = ' + uInt8Array.byteLength);
};

但是,拷贝方式发送二进制数据,会造成性能问题。比如,主线程向 Worker 发送一个 500MB 文件,默认情况下浏览器会生成一个原文件的拷贝。为了解决这个问题,JavaScript 允许主线程把二进制数据直接转移给子线程,但是一旦转移,主线程就无法再使用这些二进制数据了,这是为了防止出现多个线程同时修改数据的麻烦局面。这种转移数据的方法,叫做Transferable Objects。这使得主线程可以快速把数据交给 Worker,对于影像处理、声音处理、3D 运算等就非常方便了,不会产生性能负担。

如果要直接转移数据的控制权,就要使用下面的写法。

// Transferable Objects 格式
worker.postMessage(arrayBuffer, [arrayBuffer]);// 例子
var ab = new ArrayBuffer(1);
worker.postMessage(ab, [ab]);

同页面的 Web Worker

通常情况下,Worker 载入的是一个单独的 JavaScript 脚本文件,但是也可以载入与主线程在同一个网页的代表。

<!DOCTYPE html><body><script id="worker" type="app/worker">addEventListener('message', function () {postMessage('some message');}, false);</script></body>
</html>

注意必须指定 <script> 标签的 type 属性是一个浏览器不认识的值。

然后,读取这一段嵌入页面的脚本,用 Worker 来处理。

var blob = new Blob([document.querySelector('#worker').textContent]);
var url = window.URL.createObjectURL(blob);
var worker = new Worker(url);worker.onmessage = function (e) {// e.data === 'some message'
}

API

主线程

浏览器原生提供 Worker() 构造函数,用来供主线程生成 Worker 线程。

var myWorker = new Worker(jsUrl, options);

Worker() 构造函数,可以接受两个参数。第一个参数是脚本的网址(必须遵守同源政策),该参数是必需的,且只能加载 JS 脚本,否则会报错。第二个参数是配置对象,该对象可选。它的一个作用就是指定 Worker 的名称,用来区分多个 Worker 线程。

// 主线程
var myWorker = new Worker('worker.js', { name : 'myWorker' });// Worker 线程
self.name // myWorker

Worker() 构造函数返回一个 Worker 线程对象,用来供主线程操作 Worker。Worker 线程对象的属性和方法如下。

  • Worker.onerror:指定 error 事件的监听函数
  • Worker.onmessage:指定 message 事件的监听函数,发送过来的数据在 Event.data 属性中
  • Worker.onmessageerror:指定 messageerror 事件的监听函数。发送的数据无法序列化成字符串时,会触发这个事件
  • Worker.postMessage():向 Worker 线程发送消息
  • Worker.terminate():立即终止 Worker 线程

Worker 线程

Web Worker 有自己的全局对象,不是主线程的 window,而是一个专门为 Worker 定制的全局对象。因此定义在 window 上面的对象和方法不是全部都可以使用。

Worker 线程有一些自己的全局属性和方法。

  • self.name: Worker 的名字。该属性只读,由构造函数指定
  • self.onmessage:指定 message 事件的监听函数
  • self.onmessageerror:指定 messageerror 事件的监听函数。发送的数据无法序列化成字符串时,会触发这个事件
  • self.close():关闭 Worker 线程
  • self.postMessage():向产生这个 Worker 线程发送消息
  • self.importScripts():加载 JS 脚本

这篇关于【JavaScript 漫游】【044】Web Worker的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/850994

相关文章

Java五子棋之坐标校正

上篇针对了Java项目中的解构思维,在这篇内容中我们不妨从整体项目中拆解拿出一个非常重要的五子棋逻辑实现:坐标校正,我们如何使漫无目的鼠标点击变得有序化和可控化呢? 目录 一、从鼠标监听到获取坐标 1.MouseListener和MouseAdapter 2.mousePressed方法 二、坐标校正的具体实现方法 1.关于fillOval方法 2.坐标获取 3.坐标转换 4.坐

Spring Cloud:构建分布式系统的利器

引言 在当今的云计算和微服务架构时代,构建高效、可靠的分布式系统成为软件开发的重要任务。Spring Cloud 提供了一套完整的解决方案,帮助开发者快速构建分布式系统中的一些常见模式(例如配置管理、服务发现、断路器等)。本文将探讨 Spring Cloud 的定义、核心组件、应用场景以及未来的发展趋势。 什么是 Spring Cloud Spring Cloud 是一个基于 Spring

Javascript高级程序设计(第四版)--学习记录之变量、内存

原始值与引用值 原始值:简单的数据即基础数据类型,按值访问。 引用值:由多个值构成的对象即复杂数据类型,按引用访问。 动态属性 对于引用值而言,可以随时添加、修改和删除其属性和方法。 let person = new Object();person.name = 'Jason';person.age = 42;console.log(person.name,person.age);//'J

java8的新特性之一(Java Lambda表达式)

1:Java8的新特性 Lambda 表达式: 允许以更简洁的方式表示匿名函数(或称为闭包)。可以将Lambda表达式作为参数传递给方法或赋值给函数式接口类型的变量。 Stream API: 提供了一种处理集合数据的流式处理方式,支持函数式编程风格。 允许以声明性方式处理数据集合(如List、Set等)。提供了一系列操作,如map、filter、reduce等,以支持复杂的查询和转

Java面试八股之怎么通过Java程序判断JVM是32位还是64位

怎么通过Java程序判断JVM是32位还是64位 可以通过Java程序内部检查系统属性来判断当前运行的JVM是32位还是64位。以下是一个简单的方法: public class JvmBitCheck {public static void main(String[] args) {String arch = System.getProperty("os.arch");String dataM

详细分析Springmvc中的@ModelAttribute基本知识(附Demo)

目录 前言1. 注解用法1.1 方法参数1.2 方法1.3 类 2. 注解场景2.1 表单参数2.2 AJAX请求2.3 文件上传 3. 实战4. 总结 前言 将请求参数绑定到模型对象上,或者在请求处理之前添加模型属性 可以在方法参数、方法或者类上使用 一般适用这几种场景: 表单处理:通过 @ModelAttribute 将表单数据绑定到模型对象上预处理逻辑:在请求处理之前

eclipse运行springboot项目,找不到主类

解决办法尝试了很多种,下载sts压缩包行不通。最后解决办法如图: help--->Eclipse Marketplace--->Popular--->找到Spring Tools 3---->Installed。

JAVA读取MongoDB中的二进制图片并显示在页面上

1:Jsp页面: <td><img src="${ctx}/mongoImg/show"></td> 2:xml配置: <?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001

Java面试题:通过实例说明内连接、左外连接和右外连接的区别

在 SQL 中,连接(JOIN)用于在多个表之间组合行。最常用的连接类型是内连接(INNER JOIN)、左外连接(LEFT OUTER JOIN)和右外连接(RIGHT OUTER JOIN)。它们的主要区别在于它们如何处理表之间的匹配和不匹配行。下面是每种连接的详细说明和示例。 表示例 假设有两个表:Customers 和 Orders。 Customers CustomerIDCus

22.手绘Spring DI运行时序图

1.依赖注入发生的时间 当Spring loC容器完成了 Bean定义资源的定位、载入和解析注册以后,loC容器中已经管理类Bean 定义的相关数据,但是此时loC容器还没有对所管理的Bean进行依赖注入,依赖注入在以下两种情况 发生: 、用户第一次调用getBean()方法时,loC容器触发依赖注入。 、当用户在配置文件中将<bean>元素配置了 lazy-init二false属性,即让