用 Web Worker 将计算移出页面主线程

Web Worker 让网页可以把 JavaScript 放到后台线程中执行。计算在工作线程进行时,主线程仍能处理界面事件。Worker 也能通过 fetch 或 XMLHttpRequest 发出网络请求,并用消息把结果交回创建它的脚本;主线程同样可以给 Worker 发送任务。

本文根据 MDN 的 Using Web Workers 全文翻译整理,保留专用线程、共享线程、错误处理、数据复制与转移、内嵌 Worker、后台计算及调试等内容。页面标注的最后修改时间为 2026-08-27,不将其当作首次发表时间。所有代码均只做静态审查;修正或补充处明确标注,未声称运行测试。

主线程与 Web Worker 之间通过 postMessage 发送任务和结果,主线程操作 DOM,Worker 负责计算;底部区分复制、转移和共享内存
未完纪绘制的消息通信示意图,依据 MDN 文档;不是浏览器性能截图。

Worker 有自己的全局环境

new Worker() 接收工作脚本的 URL,在另一个全局上下文中运行它。专用 Worker 使用 DedicatedWorkerGlobalScope,共享 Worker 使用 SharedWorkerGlobalScope。在 Worker 内可以使用 self 指向当前全局环境;它没有页面的 window,直接访问 window 会出错。

工作线程不能直接修改 DOM,也不能随意使用页面对象或所有 Window 方法;但可以使用许多通用能力,如标准 JavaScript 对象、定时器、WebSocket 和 IndexedDB。能否使用某个 API,应以它的 Worker 支持说明为准。XMLHttpRequest 虽然可用,但它的 responseXML 始终为 null。

双方通过 postMessage() 发出消息,在 message 事件或 onmessage 中读取 event.data。通常交换的是数据副本,不是任意共享 JavaScript 对象。Worker 可以再创建子 Worker;子脚本需要符合原点约束,子 Worker 的相对 URL 按父 Worker 的位置解析,而不是按页面位置解析。

专用 Worker:从两个数字的一次往返开始

专用 Worker 由创建它的脚本持有和访问。MDN 的入门例子把两个输入值发送给独立脚本,工作线程相乘后返回结果。这个算式刻意保持简单,用来展示协议和界面分工,不能用它证明开启线程会加速。启动线程、复制消息和调度都需要成本。

原文先用 if (window.Worker) 检测能力,再用 new Worker("worker.js") 创建实例。普通相对 URL 通常相对页面解析;使用 webpack、Vite、Parcel 等构建工具时,原文建议写成:

const myWorker = new Worker(new URL("worker.js", import.meta.url));

这样 URL 相对当前模块文件解析,构建工具也能更可靠地识别和重命名工作脚本。使用 import.meta 的主脚本须以 ES 模块方式加载。不要混淆:主脚本是模块,并不自动让 Worker 脚本成为模块;若工作脚本需要静态 import,创建时应显式设置 { type: "module" }。

一个补齐元素绑定的版本

编者修订:下面保持原文“发送两个值、返回字符串、textContent 显示”的协议,补齐 HTML 元素与脚本绑定,并增加输入、错误与停止处理。它是便于阅读的修订版,不是未经改动的原文代码,也不是已经执行验证的成品。

<!-- index.html 中的正文与脚本入口 -->
<label>第一个数 <input id="first" type="number"></label>
<label>第二个数 <input id="second" type="number"></label>
<p id="result" aria-live="polite"></p>
<button id="stop" type="button">停止工作线程</button>
<script type="module" src="./main.js"></script>
// main.js
const first = document.querySelector("#first");
const second = document.querySelector("#second");
const result = document.querySelector("#result");
const stop = document.querySelector("#stop");

if (!first || !second || !result || !stop) {
  throw new Error("页面缺少示例需要的元素");
}

if (!window.Worker) {
  result.textContent = "当前浏览器不支持 Web Worker";
  stop.disabled = true;
} else {
  try {
    const myWorker = new Worker(new URL("./worker.js", import.meta.url));
    let stopped = false;

    [first, second].forEach((input) => {
      input.addEventListener("change", () => {
        if (!stopped) myWorker.postMessage([first.value, second.value]);
      });
    });

    myWorker.onmessage = (event) => {
      result.textContent = String(event.data);
    };
    myWorker.onerror = (event) => {
      result.textContent = `工作线程出错:${event.message}`;
    };
    myWorker.onmessageerror = () => {
      result.textContent = "无法反序列化工作线程消息";
    };
    stop.addEventListener("click", () => {
      myWorker.terminate();
      stopped = true;
      stop.disabled = true;
      result.textContent = "工作线程已停止";
    });
  } catch (error) {
    result.textContent = `无法创建工作线程:${error.message}`;
    stop.disabled = true;
  }
}
// worker.js(经典 Worker 脚本)
self.onmessage = (event) => {
  const values = event.data;
  if (!Array.isArray(values) || values.length !== 2 ||
      values.some((v) => typeof v !== "string" || v.trim() === "")) {
    self.postMessage("请输入两个数字");
    return;
  }
  const [a, b] = values.map(Number);
  const value = a * b;
  if (![a, b, value].every(Number.isFinite)) {
    self.postMessage("输入或结果超出有限数值范围");
    return;
  }
  self.postMessage(`Result: ${value}`);
};

主线程使用 myWorker.postMessage 与 myWorker.onmessage;工作脚本则在自己的全局环境使用 self.postMessage 与 self.onmessage。返回值交给 textContent,不会被当作 HTML 插入。示例中的输入值是字符串,因此不能仅依赖乘法的隐式转换;修订版拒绝空字符串和非有限结果。

终止、错误与加载脚本

主线程可以调用 myWorker.terminate() 立即终止工作线程。它不会等待当前任务优雅收尾,所以需要保存的状态应在终止前通过明确协议处理,不能指望被终止的代码继续执行清理。

Worker 运行时错误会触发 error 事件,事件提供可读的 message、脚本 filename 和行号 lineno。该事件不冒泡、可以取消;调用 preventDefault 可以阻止默认处理,但不等于问题已恢复。反序列化失败则可通过 messageerror 单独处理,后者是本稿补充。

经典 Worker 可以用同步的 importScripts() 加载脚本。例如不传参数不加载任何内容;传入一个或多个 URL,会依次执行对应脚本:

importScripts();
importScripts("foo.js");
importScripts("foo.js", "bar.js");

脚本可能按不同顺序下载,但按参数顺序执行;全部加载与执行完成前 importScripts 不返回。原文将加载失败描述为 NETWORK_ERROR,并说明后续流程会中断,但此前已经执行的代码与已排队的任务不因此自动回滚。模块 Worker 应使用模块导入机制,不能把 importScripts 的用法照搬过去。

编者安全注:原文还展示了协议相对的第三方脚本地址。加载脚本就意味着授予其在 Worker 中执行的能力,Worker 仍能访问网络与部分存储,并非运行不可信脚本的安全沙箱。本稿不把未知第三方地址写入可直接复制的示例;应使用经审查、固定来源的脚本,避免把用户输入拼成脚本 URL。

共享 Worker:多个页面通过端口连接同一线程

SharedWorker 可以被多个脚本、窗口、iframe 或 Worker 访问。访问者必须具有完全相同的原点,也就是协议、主机和端口相同。原文特别指出,Firefox 中普通窗口与隐私窗口之间不会共享同一个 SharedWorker;具体浏览器和上下文规则仍需按目标环境核对。

创建方式为 new SharedWorker("worker.js"),区别在于消息要通过显式的 port 交换。专用 Worker 的这层端口由接口隐含处理。使用 port.onmessage 会隐式启动端口;若改用 addEventListener 监听 message,则需要调用 port.start()。双向通信时,两端都要正确启动端口。

原文使用两个页面:一个把两个数相乘,另一个求一个数的平方。以下按原文说明展示核心连接,元素均显式查询;输入控件与结果节点应由各自页面提供。

// 乘法页面:需要 #first、#second、#result
const first = document.querySelector("#first");
const second = document.querySelector("#second");
const result = document.querySelector("#result");
const shared = new SharedWorker("shared-worker.js");
shared.port.onmessage = (event) => {
  result.textContent = String(event.data);
};
[first, second].forEach((input) => {
  input.onchange = () => {
    shared.port.postMessage([first.value, second.value]);
  };
});
// 平方页面:需要 #square-number、#square-result
const squareNumber = document.querySelector("#square-number");
const squareResult = document.querySelector("#square-result");
const shared = new SharedWorker("shared-worker.js");
shared.port.onmessage = (event) => {
  squareResult.textContent = String(event.data);
};
squareNumber.onchange = () => {
  shared.port.postMessage([squareNumber.value, squareNumber.value]);
};
// shared-worker.js;校验为编者补充
self.onconnect = (event) => {
  const port = event.ports[0];
  port.onmessage = (message) => {
    const values = message.data;
    if (!Array.isArray(values) || values.length !== 2 ||
        values.some((v) => typeof v !== "string" || v.trim() === "")) {
      port.postMessage("请输入两个数字");
      return;
    }
    const [a, b] = values.map(Number);
    const product = a * b;
    port.postMessage([a, b, product].every(Number.isFinite)
      ? `Result: ${product}` : "输入或结果超出有限数值范围");
  };
};

新连接触发 onconnect,通过 event.ports[0] 取得该连接端口;每次返回也用这个端口,因此结果回到对应调用者。上面两段页面代码是两个独立文件,不能把同名 const 声明全部粘贴到同一个作用域。它们为说明端口差异而省略了入门示例已展示的特性检测、缺失元素和构造异常处理,实际应用仍要补齐。

当没有窗口、iframe 或 Worker 再引用共享线程时,它会被关闭。浏览器可能为同源导航短暂保留它以减少重启成本。当前原文还介绍 new SharedWorker("worker.js", { extendedLifetime: true }),请求在最后一个引用关闭后短暂继续工作,例如写入状态;这不是永久驻留承诺,也不能据此保证页面关闭后的数据一定写入成功。该选项需另查目标浏览器支持。

线程安全与内容安全策略

Worker 使用真正的操作系统线程,但与页面的通信点受控,而且不能直接触碰 DOM,因此普通消息传递避免了许多任意共享可变状态的问题。这不表示所有并发问题都会消失:异步结果先后、过期任务和显式共享内存仍需应用自行管理。

Worker 有独立的执行上下文,通常不直接继承创建它的文档或父 Worker 的内容安全策略(CSP)。原文举例说,文档的 Content-Security-Policy: script-src 'self' 限制该文档使用 eval,并不自动让独立 Worker 采用相同限制。若要约束 Worker 内部执行,应在提供 Worker 脚本的 HTTP 响应中设置对应 CSP。URL 使用 data 或 blob 等特殊来源时,原文说明它会继承创建方的 CSP。

编者补充:“Worker 内部采用什么 CSP”和“页面允许创建哪些 Worker”是两个问题。实际部署时还应核对页面的 worker-src 等创建策略,不能把独立执行上下文理解为可以绕过所有 CSP。本文没有配置放宽策略、使用 eval,或运行外部脚本。

结构化复制:对象不是同一个实例

主线程给 Worker 的对象通常会被序列化,在另一端反序列化;双方得到不同实例。多数浏览器通过结构化复制算法实现它。算法支持 JSON 能表示的数据,还支持 Date、循环引用等额外类型,但并非任意值都能发送,函数等不支持的值会失败。

原文以 Date 为例。主线程发送:

const myWorker = new Worker("my-task.js");
myWorker.onmessage = (event) => {
  console.log(`Worker said: "${event.data}"`);
};
myWorker.postMessage({ lastUpdate: new Date() });
// my-task.js
self.onmessage = (event) => {
  self.postMessage(`Last updated: ${event.data.lastUpdate.toDateString()}`);
};

这个例子依赖约定的数据结构。面对更复杂的消息,仍需检查消息类型、字段和可接受的大小;结构化复制不会自动替你验证业务含义,也不自动限制资源消耗。

给消息加上方法名:一个可查询的分派器

如果工作线程和主线程都要处理多种动作,可以约定三个字段:queryMethod 表示请求动作,queryMethodListener 表示返回后调用的监听器,queryMethodArguments 表示参数数组。字段可以改名,但两端必须一致。原文封装的 QueryableWorker 提供 sendQuery、postMessage、terminate、addListener、removeListener 以及默认消息监听器。

静态审查发现:原文先检查消息是否有字段,随后直接用消息中的名称索引普通对象并执行 apply。它没有确认索引结果是自有、允许调用的方法,也没有检查参数是不是数组;未知名称会触发异常,继承属性也可能被取到。原文完整例子的 printStuff 还寻找不存在于所给 HTML 中的 firstLink,会导致空引用。前面的分步片段使用 addListeners/removeListeners,完整版本却使用单数 addListener/removeListener,拼接时也要统一。

下面给出明确标为编者修订版的完整核心实现:用 Map 保存允许的方法与监听器,验证消息结构及动作参数,修正元素 ID,同时保留原文的“求差”和“等待三秒返回”两个动作。没有使用 eval、Function 构造器或从消息生成代码。

<!-- query-example.html 的内容 -->
<button id="first-action" type="button">计算 5 与 3 的差</button>
<button id="second-action" type="button">等待三秒</button>
<button id="terminate" type="button">停止</button>
<p id="difference"></p>
<p id="notice"></p>
<script type="module" src="./query-main.js"></script>
// query-main.js(修订版)
class QueryableWorker {
  constructor(url, defaultListener = () => {}, onError = console.error) {
    this.worker = new Worker(url);
    this.listeners = new Map();
    this.defaultListener = defaultListener;
    this.stopped = false;
    this.worker.onerror = onError;
    this.worker.onmessageerror = () => onError(new Error("消息读取失败"));
    this.worker.onmessage = ({ data }) => {
      if (data && typeof data === "object" &&
          Object.hasOwn(data, "queryMethodListener") &&
          Object.hasOwn(data, "queryMethodArguments")) {
        const name = data.queryMethodListener;
        const args = data.queryMethodArguments;
        if (typeof name !== "string" || !Array.isArray(args) || args.length > 8) {
          onError(new Error("响应格式无效"));
          return;
        }
        const listener = this.listeners.get(name);
        if (typeof listener !== "function") {
          onError(new Error("未知响应类型"));
          return;
        }
        try { listener(...args); } catch (error) { onError(error); }
      } else {
        this.defaultListener(data);
      }
    };
  }
  addListener(name, listener) {
    if (typeof name !== "string" || typeof listener !== "function") {
      throw new TypeError("监听器需要字符串名称与函数");
    }
    this.listeners.set(name, listener);
  }
  removeListener(name) { this.listeners.delete(name); }
  postMessage(data) {
    if (this.stopped) throw new Error("工作线程已停止");
    this.worker.postMessage(data);
  }
  sendQuery(name, ...args) {
    if (typeof name !== "string" || !name || args.length > 8) {
      throw new TypeError("请求格式无效");
    }
    this.postMessage({ queryMethod: name, queryMethodArguments: args });
  }
  terminate() { this.worker.terminate(); this.stopped = true; }
}

const difference = document.querySelector("#difference");
const notice = document.querySelector("#notice");
const firstButton = document.querySelector("#first-action");
const secondButton = document.querySelector("#second-action");
const stopButton = document.querySelector("#terminate");
if (![difference, notice, firstButton, secondButton, stopButton].every(Boolean)) {
  throw new Error("页面缺少示例元素");
}
const report = (error) => { notice.textContent = String(error.message); };
const myTask = new QueryableWorker(
  new URL("./query-worker.js", import.meta.url),
  (data) => { notice.textContent = String(data); },
  report,
);
myTask.addListener("printStuff", (value) => {
  if (!Number.isFinite(value)) throw new TypeError("差值无效");
  difference.textContent = `The difference is ${value}!`;
});
myTask.addListener("doAlert", (time, unit) => {
  if (time !== 3 || unit !== "seconds") throw new TypeError("等待响应无效");
  notice.textContent = `Worker waited for ${time} ${unit}`;
});
myTask.addListener("error", (message) => {
  notice.textContent = String(message);
});
firstButton.onclick = () => myTask.sendQuery("getDifference", 5, 3);
secondButton.onclick = () => myTask.sendQuery("waitSomeTime");
stopButton.onclick = () => {
  myTask.terminate();
  [firstButton, secondButton, stopButton].forEach((b) => { b.disabled = true; });
  notice.textContent = "已停止";
};
// query-worker.js(修订版)
const reply = (name, ...args) => self.postMessage({
  queryMethodListener: name,
  queryMethodArguments: args,
});
let waiting = false;
const actions = new Map([
  ["getDifference", (a, b) => {
    if (![a, b].every(Number.isFinite) || !Number.isFinite(a - b)) {
      throw new TypeError("只接受有限数值与有限结果");
    }
    reply("printStuff", a - b);
  }],
  ["waitSomeTime", () => {
    if (waiting) throw new Error("已有等待任务");
    waiting = true;
    setTimeout(() => {
      waiting = false;
      reply("doAlert", 3, "seconds");
    }, 3000);
  }],
]);
const arities = new Map([["getDifference", 2], ["waitSomeTime", 0]]);
self.onmessage = ({ data }) => {
  if (!data || typeof data !== "object" ||
      !Object.hasOwn(data, "queryMethod") ||
      !Object.hasOwn(data, "queryMethodArguments")) {
    // 对应原文 defaultReply 分支;此示例不接受额外公共动作。
    reply("error", "消息缺少请求字段");
    return;
  }
  const name = data.queryMethod;
  const args = data.queryMethodArguments;
  if (typeof name !== "string" || !actions.has(name) ||
      !Array.isArray(args) || args.length !== arities.get(name)) {
    reply("error", "未知动作或参数数量不符");
    return;
  }
  try { actions.get(name)(...args); }
  catch (error) { reply("error", String(error.message)); }
};

修订版把原来的弹窗改为页面文字,保留 doAlert 消息名以便对照;同时只允许一个待完成的三秒计时任务,避免连续点击无限累积定时器。这仍是教学级协议,没有加入请求 ID、取消单项任务、超时重试、大小上限或工作池。若有多个并发任务,应将请求和响应明确配对,并处理过期结果;这些功能不能从“使用了 Worker”自动获得。

转移所有权:发送大缓冲区时不复制内容

某些对象支持作为 transferables 转移到另一上下文。原文以 ArrayBuffer 为例:发送时把同一个 buffer 放入转移列表,它的所有权转给 Worker,原发送端的 ArrayBuffer 被分离,不能继续当作原来的存储使用。原文展示一个 32 MiB 字节数组:

const bytes = new Uint8Array(1024 * 1024 * 32).map((value, index) => index);
worker.postMessage(bytes.buffer, [bytes.buffer]);
// 从这里起,发送端不应继续使用原来的 buffer。

这里传递的是 buffer 本身,第二个参数列出需要转移的对象;只把对象写入转移列表而没有通过消息让接收方取得它,不能形成有用的数据协议。原文所说的零拷贝针对这类可转移数据的所有权移动,不表示所有消息、所有对象或整条应用处理链都完全没有复制开销。上面 32 MiB 分配只用于解释容量,本文未执行。

共享内存是另外一回事

SharedArrayBuffer 允许主线程和 Worker 同时访问同一片内存,不必为每次数据变化都通过消息复制。它也重新引入了同步、确定性、安全和性能问题。不要把 ArrayBuffer 的 transfer 当成 SharedArrayBuffer,也不要只因为“后台线程”就假定读写顺序已经受保护。

若采用共享内存,需按具体 API 核对跨源隔离要求,并用 Atomics 等机制设计一致的同步协议。本稿只解释原文的区别,不提供未经验证的共享内存算法,也不通过放宽安全头来追求兼容性。

把 Worker 代码放进页面:数据块与 Blob

HTML 没有一种等同于普通可执行 script 标签的专用“内嵌 Worker”语法。原文利用没有 src、且 type 不是可执行 JavaScript MIME 类型的 script 元素作为纯数据块:读取这些元素的 textContent,合成 JavaScript Blob,再用对象 URL 创建 Worker。该技术允许把多段文本按顺序组合到同一个工作脚本中。

编者修订:下面缩短为一个数据块,显式绑定元素并提供清理按钮。它保留原文 Hello World 的消息往返,但没有复用原页的多个内联执行脚本。代码只是显示在本文中的转义示例,不会随文章页面执行。

<!-- embedded.html -->
<script id="worker-source" type="text/js-worker">
  self.onmessage = () => self.postMessage("Hello World!");
</script>
<p id="log"></p>
<button id="cleanup" type="button">结束并释放</button>
<script type="module" src="./embedded-main.js"></script>
// embedded-main.js
const source = document.querySelector("#worker-source");
const log = document.querySelector("#log");
const cleanup = document.querySelector("#cleanup");
const blob = new Blob([source.textContent], { type: "text/javascript" });
const objectURL = URL.createObjectURL(blob);
let worker;
try {
  worker = new Worker(objectURL);
  worker.onmessage = ({ data }) => { log.textContent = String(data); };
  worker.postMessage("");
} catch (error) {
  URL.revokeObjectURL(objectURL);
  throw error;
}
cleanup.onclick = () => {
  worker.terminate();
  URL.revokeObjectURL(objectURL);
  cleanup.disabled = true;
};

原文通过 DocumentFragment 组合日志文本和换行,减少重复 DOM 更新;这与 Worker 本身是两件事。原文还给出把函数转换为 Blob URL 的工具:

function fn2workerURL(fn) {
  const blob = new Blob([`(${fn.toString()})()`], { type: "text/javascript" });
  return URL.createObjectURL(blob);
}

这会把函数文本放进另一个执行环境,不能保留外部闭包变量;生成的对象 URL 在不再使用时应释放。只能用受控源码,不要把用户提供的脚本文本或函数文本当成可执行输入。Blob Worker 还受 CSP 等约束,不能用它绕过部署策略。

此外,原文在内嵌示例后写到 document.worker 属性,但展示的代码只声明了 const worker,并没有给 document.worker 赋值。本稿按实际代码解释,不重复这一不一致说法。

后台计算实例:斐波那契数列

原文另用斐波那契数列展示表单提交后把计算交给工作线程:下标 0 的值为 0,下标 6 的值为 8。工作脚本用迭代法维护 a、b,页面只发送输入并显示结果。

静态审查:原始循环对负数、非整数和无限值没有完整限制;如果传入 Infinity,递减后仍是 Infinity,循环无法结束。原文使用 Number,还会在较大下标失去整数精度。以下修订版限制输入为 0–78 的整数,使结果处于 JavaScript 安全整数范围;这是一项明确收紧的教学限制,非原文原有条件。

// fibonacci.js(修订版,使用 Number)
self.onmessage = ({ data }) => {
  const text = typeof data === "string" ? data.trim() : "";
  const num = Number(text);
  if (text === "" || !Number.isInteger(num) || num < 0 || num > 78) {
    self.postMessage({ ok: false, message: "请输入 0–78 的整数" });
    return;
  }
  let a = 1;
  let b = 0;
  for (let i = 0; i < num; i++) [a, b] = [a + b, a];
  self.postMessage({ ok: true, value: b });
};
<!-- fibonacci 页面正文 -->
<form id="fib-form">
  <label for="number">斐波那契数列下标(0–78)</label>
  <input type="number" id="number" min="0" max="78" step="1" required>
  <button type="submit">计算</button>
</form>
<p id="fib-result"></p>
<script type="module" src="./fibonacci-main.js"></script>
// fibonacci-main.js
const form = document.querySelector("#fib-form");
const input = document.querySelector("#number");
const result = document.querySelector("#fib-result");
const worker = new Worker(new URL("./fibonacci.js", import.meta.url));
worker.onmessage = ({ data }) => {
  result.textContent = data.ok ? String(data.value) : data.message;
};
worker.onerror = (event) => { result.textContent = `Worker error: ${event.message}`; };
form.onsubmit = (event) => {
  event.preventDefault();
  worker.postMessage(input.value);
  input.value = "";
};

页面约束只是交互提示,工作线程自身仍保留校验。这里把结果协议从原文的裸数值改成 ok/value 或 ok/message,便于清楚显示错误。主脚本的特性检测、元素存在性和构造异常处理可沿用前面的专用 Worker 示例。正文中的 6 → 8 是数列定义示例,不是运行报告。

对于计算密集型任务,多核机器还可以把工作拆给多个 Worker。但拆分方式、线程数量、内存占用和传输成本需要实测;不能无限创建线程,也不能把一个算法的理论并行性直接当作应用加速结论。

其他后台类型与调试

Service Worker 的主要作用是介于应用、浏览器和网络之间的请求代理,用于离线体验、请求拦截、推送与后台同步等场景;它不是把任意计算永久驻留后台的替代方案。Audio Worklet 则提供专门的音频处理执行环境,与通用 Worker 的职责不同。

浏览器的 JavaScript 调试器通常能显示主线程与活动工作线程的源码,支持断点和日志点。可以参看 Chrome Sources 文档 与 Firefox 调试器文档。原文列出的检查入口包括 edge://inspect/、chrome://inspect/ 和 about:debugging#/runtime/this-firefox;具体列出哪些后台目标会随浏览器与版本变化,应按脚本 URL 找到正确目标。开发者工具里没有对应日志,不一定说明页面线程从未收到结果,也可能是正在查看另一个执行上下文。

Worker 中可使用 Array、Date、Math、String、fetch、setTimeout 和 setInterval 等大量标准功能,以及对应的 Worker 导航信息接口;不能直接拿页面的 DOM 或 Window 对象来操作。需要改变页面时,发送结果给主线程,再由页面的事件处理器完成修改。完整清单见 MDN:Worker 可用的函数和类,接口规范见 HTML Workers 规范。

归属、许可与本文修改

原文 Using Web Workers 由 MDN contributors / Mozilla Contributors 共同维护。本文为未完纪中文翻译整理,替换示意图,合并原文重复的渐进式代码,并对明确指出的代码缺口作修订;正文按 CC BY-SA 2.5 或其后版本 的要求保留署名与相同方式共享。

依据 MDN 的版权与许可说明,2010-08-20 及以后新增的代码示例采用 CC0,更早示例可能为 MIT;该说明也承认当前页面不总能直接辨认每段代码首次加入时间。本稿保留这一条件,不把所有历史代码一概重新标为 CC0。本文新增示意图单独注明未完纪绘制。

本次没有执行 JavaScript、导入外部脚本、开启服务或做性能测试。静态检查发现的问题已在对应位置列出;未发现不等于不存在。修改后的代码同样仍需在读者自己的隔离开发环境中进行功能、兼容性和性能验证。

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容