2025-04-22 日报 Day164

2025-04-22 日报 Day164

Yuyang 前端小白🥬

今日的鸡汤

努力是一件特别需要沉下心来,长久坚持的事。它成长的土壤需要一个人单打独斗,忍受无数个孤独和寂寞的日子,当你真正发自内心想要做成某件事,就不会太在乎要不要晒给别人看。内在驱动力足够支撑你,无论遇到任何困难和挫折都能坚持到底。

今日学习内容

1、JS 红皮书 P791-795 第二十七章:工作者线程

今日笔记

1、前端开发者常说:“JavaScript 是单线程的。”这种说法虽然有些简单,但描述了 JavaScript 在浏览器中的一般行为。因此,作为帮助 Web 开发人员理解 JavaScript 的教学工具,它非常有用。
单线程就意味着不能像多线程语言那样把工作委托给独立的线程或进程去做。JavaScript 的单线程可以保证它与不同浏览器 API 兼容。假如 JavaScript 可以多线程执行并发更改,那么像 DOM 这样的 API就会出现问题。因此,POSIX 线程或 Java 的 Thread 类等传统并发结构都不适合 JavaScript。
而这也正是工作者线程的价值所在:允许把主线程的工作转嫁给独立的实体,而不会改变现有的单线程模型。虽然本章要介绍的各种工作者线程有不同的形式和功能,但它们的共同的特点是都独立于JavaScript 的主执行环境。
2、工作者线程简介: JavaScript 环境实际上是运行在托管操作系统中的虚拟环境。在浏览器中每打开一个页面,就会分配一个它自己的环境。这样,每个页面都有自己的内存、事件循环、DOM,等等。每个页面就相当于一个沙盒,不会干扰其他页面。对于浏览器来说,同时管理多个环境是非常简单的,因为所有这些环境都是并行执行的。
使用工作者线程,浏览器可以在原始页面环境之外再分配一个完全独立的二级子环境。这个子环境不能与依赖单线程交互的 API(如 DOM)互操作,但可以与父环境并行执行代码。

  • 工作者线程与线程:  工作者线程是以实际线程实现的。例如,Blink 浏览器引擎实现工作者线程的 WorkerThread 就对应着底层的线程。
     工作者线程并行执行。虽然页面和工作者线程都是单线程 JavaScript 环境,每个环境中的指令则可以并行执行。
     工作者线程可以共享某些内存。工作者线程能够使用 SharedArrayBuffer 在多个环境间共享内容。虽然线程会使用锁实现并发控制,但 JavaScript 使用 Atomics 接口实现并发控制。
    工作者线程与线程有很多类似之处,但也有重要的区别。
     工作者线程不共享全部内存。在传统线程模型中,多线程有能力读写共享内存空间。除了 SharedArrayBuffer 外,从工作者线程进出的数据需要复制或转移。
     工作者线程不一定在同一个进程里。通常,一个进程可以在内部产生多个线程。根据浏览器引擎的实现,工作者线程可能与页面属于同一进程,也可能不属于。例如,Chrome 的 Blink 引擎对共享工作者线程和服务工作者线程使用独立的进程。
     创建工作者线程的开销更大。工作者线程有自己独立的事件循环、全局对象、事件处理程序和其他 JavaScript 环境必需的特性。创建这些结构的代价不容忽视
  • 工作者线程的类型: Web 工作者线程规范中定义了三种主要的工作者线程:专用工作者线程、共享工作者线程和服务工作者线程。
    1-专用工作者线程: 专用工作者线程,通常简称为工作者线程、Web Worker 或 Worker,是一种实用的工具,可以让脚本单独创建一个 JavaScript 线程,以执行委托的任务。专用工作者线程,顾名思义,只能被创建它的页面使用。
    2-共享工作者线程: 共享工作者线程与专用工作者线程非常相似。主要区别是共享工作者线程可以被多个不同的上下文使用,包括不同的页面。任何与创建共享工作者线程的脚本同源的脚本,都可以向共享工作者线程发送消息或从中接收消息。
    3-服务工作者线程:服务工作者线程与专用工作者线程和共享工作者线程截然不同。它的主要用途是拦截、重定向和修改页面发出的请求,充当网络请求的仲裁者的角色。
  • WorkerGlobalScope: 在网页上,window 对象可以向运行在其中的脚本暴露各种全局变量。在工作者线程内部,没有 window的概念。这里的全局对象是 WorkerGlobalScope 的实例,通过 self 关键字暴露出来。
    1-WorkerGlobalScope属性和方法: self 上可用的属性是 window 对象上属性的严格子集。其中有些属性会返回特定于工作者线程的版本。
     navigator:返回与工作者线程关联的 WorkerNavigator。
     self:返回 WorkerGlobalScope 对象。
     location:返回与工作者线程关联的 WorkerLocation。
     performance:返回(只包含特定属性和方法的)Performance 对象。
     console:返回与工作者线程关联的 Console 对象;对 API 没有限制。
     caches:返回与工作者线程关联的 CacheStorage 对象;对 API 没有限制。
     indexedDB:返回 IDBFactory 对象。
     isSecureContext:返回布尔值,表示工作者线程上下文是否安全。
     origin:返回 WorkerGlobalScope 的源。
    类似地,self 对象上暴露的一些方法也是 window 上方法的子集。这些 self 上的方法也与 window上对应的方法操作一样。
     atob()
     btoa()
     clearInterval()
     clearTimeout()
     createImageBitmap()
     fetch()
     setInterval()
     setTimeout()
    WorkerGlobalScope 还增加了新的全局方法 importScripts(),只在工作者线程内可用。本章稍后会介绍该方法。
    2-WorkerGlobalScope 的子类
    实际上并不是所有地方都实现了 WorkerGlobalScope。每种类型的工作者线程都使用了自己特定的全局对象,这继承自 WorkerGlobalScope。
     专用工作者线程使用 DedicatedWorkerGlobalScope。
     共享工作者线程使用 SharedWorkerGlobalScope。
     服务工作者线程使用 ServiceWorkerGlobalScope。
    本章稍后会在这些全局对象对应的小节中讨论其差异。
    3、专用工作者线程: 专用工作者线程是最简单的 Web 工作者线程,网页中的脚本可以创建专用工作者线程来执行在页面线程之外的其他任务。这样的线程可以与父页面交换信息、发送网络请求、执行文件输入/输出、进行密集计算、处理大量数据,以及实现其他不适合在页面执行线程里做的任务(否则会导致页面响应迟钝)。
  • 专用工作者线程的基本概念: 可以把专用工作者线程称为后台脚本(background script)。JavaScript 线程的各个方面,包括生命周期管理、代码路径和输入/输出,都由初始化线程时提供的脚本来控制。该脚本也可以再请求其他脚本,但一个线程总是从一个脚本源开始。
    1-创建专用工作者线程: 创建专用工作者线程最常见的方式是加载 JavaScript 文件。把文件路径提供给 Worker 构造函数,然后构造函数再在后台异步加载脚本并实例化工作者线程。传给构造函数的文件路径可以是多种形式。
    下面的代码演示了如何创建空的专用工作者线程:
    emptyWorker.js
    // 空的 JS 工作者线程文件
    main.js
    console.log(location.href); // “https://example.com/
    const worker = new Worker(location.href + ‘emptyWorker.js’);
    console.log(worker); // Worker {}
    这个例子非常简单,但涉及几个基本概念。
     emptyWorker.js 文件是从绝对路径加载的。根据应用程序的结构,使用绝对 URL 经常是多余的。
     这个文件是在后台加载的,工作者线程的初始化完全独立于 main.js。
     工作者线程本身存在于一个独立的 JavaScript 环境中,因此 main.js 必须以 Worker 对象为代理实现与工作者线程通信。在上面的例子中,该对象被赋值给了 worker 变量。
     虽然相应的工作者线程可能还不存在,但该 Worker 对象已在原始环境中可用了。
    前面的例子可修改为使用相对路径。不过,这要求 main.js 必须与 emptyWorker.js 在同一个路径下:
    const worker = new Worker(‘./emptyWorker.js’);
    console.log(worker); // Worker {}
    2-工作者线程安全限制: 工作者线程的脚本文件只能从与父页面相同的源加载。从其他源加载工作者线程的脚本文件会导致错误,如下所示:
    // 尝试基于 https://example.com/worker.js 创建工作者线程
    const sameOriginWorker = new Worker(‘./worker.js’);
    // 尝试基于 https://untrusted.com/worker.js 创建工作者线程
    const remoteOriginWorker = new Worker(‘https://untrusted.com/worker.js ‘);
    // Error: Uncaught DOMException: Failed to construct ‘Worker’:
    // Script at https://untrusted.com/main.js cannot be accessed
    // from origin https://example.com
    注意 不能使用非同源脚本创建工作者线程,并不影响执行其他源的脚本。在工作者线程内部,使用 importScripts()可以加载其他源的脚本。
    基于加载脚本创建的工作者线程不受文档的内容安全策略限制,因为工作者线程在与父文档不同的上下文中运行。不过,如果工作者线程加载的脚本带有全局唯一标识符(与加载自一个二进制大文件一样),就会受父文档内容安全策略的限制。
    3-使用worker对象: Worker()构造函数返回的 Worker 对象是与刚创建的专用工作者线程通信的连接点。它可用于在工作者线程和父上下文间传输信息,以及捕获专用工作者线程发出的事件。
    注意 要管理好使用 Worker()创建的每个 Worker 对象。在终止工作者线程之前,它不会被垃圾回收,也不能通过编程方式恢复对之前 Worker 对象的引用。
    Worker 对象支持下列事件处理程序属性。
     onerror:在工作者线程中发生 ErrorEvent 类型的错误事件时会调用指定给该属性的处理程序。
     该事件会在工作者线程中抛出错误时发生。
     该事件也可以通过 worker.addEventListener(‘error’, handler)的形式处理。
     onmessage:在工作者线程中发生 MessageEvent 类型的消息事件时会调用指定给该属性的处理程序。
     该事件会在工作者线程向父上下文发送消息时发生。
     该事件也可以通过使用 worker.addEventListener(‘message’, handler)处理。
     onmessageerror:在工作者线程中发生 MessageEvent 类型的错误事件时会调用指定给该属性的处理程序。
     该事件会在工作者线程收到无法反序列化的消息时发生。
     该事件也可以通过使用 worker.addEventListener(‘messageerror’, handler)处理。
    Worker 对象还支持下列方法。
     postMessage():用于通过异步消息事件向工作者线程发送信息。
     terminate():用于立即终止工作者线程。没有为工作者线程提供清理的机会,脚本会突然停止。
    4-DedicatedWorkerGlobalScope: 在专用工作者线程内部,全局作用域是 DedicatedWorkerGlobalScope 的实例。因为这继承自WorkerGlobalScope,所以包含它的所有属性和方法。工作者线程可以通过 self 关键字访问该全局作用域。
    globalScopeWorker.js
    console.log(‘inside worker:’, self);
    main.js
    const worker = new Worker(‘./globalScopeWorker.js’);
    console.log(‘created worker:’, worker);
    // created worker: Worker {}
    // inside worker: DedicatedWorkerGlobalScope {}
    如此例所示,顶级脚本和工作者线程中的 console 对象都将写入浏览器控制台,这对于调试非常有用。因为工作者线程具有不可忽略的启动延迟,所以即使 Worker 对象存在,工作者线程的日志也会在主线程的日志之后打印出来。
    注意 这里两个独立的 JavaScript 线程都在向一个 console 对象发消息,该对象随后将消息序列化并在浏览器控制台打印出来。浏览器从两个不同的 JavaScript 线程收到消息,并按照自己认为合适的顺序输出这些消息。为此,在多线程应用程序中使用日志确定操作顺序时必须要当心。
    DedicatedWorkerGlobalScope 在 WorkerGlobalScope 基础上增加了以下属性和方法。
     name:可以提供给 Worker 构造函数的一个可选的字符串标识符。
     postMessage():与 worker.postMessage()对应的方法,用于从工作者线程内部向父上下文发送消息。
     close():与 worker.terminate()对应的方法,用于立即终止工作者线程。没有为工作者线程提供清理的机会,脚本会突然停止。
     importScripts():用于向工作者线程中导入任意数量的脚本。
此页目录
2025-04-22 日报 Day164