经纬志
首页 / 实战手册 / 工程化

逻辑挪进 Worker,一加载就报 document 未定义

先看结论

把两万个点的聚合计算放进 Worker,想解放主线程,结果 Worker 刚启动就挂掉。

最后验证 2026-11-03 · 多技术栈 · Vite 5.4 · 更新 2026-10-29 · 引用本文

现象

Worker 的控制台(浏览器开发者工具的 Worker 上下文)报错,主线程收不到任何消息:

Uncaught ReferenceError: document is not defined
    at leaflet-src.esm.js:4210

Uncaught ReferenceError: window is not defined
    at proxied-module.js:1

原因

Worker 里没有 window、document、localStorage。 很多地图库在模块顶层就会探测 window 或创建 canvas, 仅仅 import 就会触发报错,哪怕没有调用任何地图 API。

另一类情况是把整个组件文件搬进 Worker,文件里连带引入了 UI 依赖, 依赖链上任意一个模块访问 DOM,都会在加载阶段失败。

排查过程

  1. 看报错栈最底部的模块名,确认是哪个依赖在顶层访问 DOM。
  2. 把 Worker 入口临时清空,只留一行日志,确认 Worker 本身可用。
  3. 逐个 import,找出触发报错的那一个。
// worker.js 排查用
console.log('worker 启动', typeof self);
// 然后逐个取消注释
// import './geo/aggregate.js';
// import 'leaflet';

解决方案

Worker 只放纯计算代码。聚合、坐标换算、网格统计这类逻辑抽成不依赖 DOM 的模块:

// geo/aggregate.js —— 不引入任何地图库
export function gridAggregate(points, cellSize) {
  var buckets = new Map();
  for (var i = 0; i < points.length; i++) {
    var key = Math.floor(points[i].x / cellSize) + ':' +
              Math.floor(points[i].y / cellSize);
    var b = buckets.get(key) || { x: 0, y: 0, count: 0 };
    b.x += points[i].x;
    b.y += points[i].y;
    b.count++;
    buckets.set(key, b);
  }
  return Array.from(buckets.values());
}
// worker.js
import { gridAggregate } from './geo/aggregate.js';

self.onmessage = function (e) {
  var result = gridAggregate(e.data.points, e.data.cellSize);
  self.postMessage({ id: e.data.id, cells: result });
};

主线程用 Vite 的 Worker 导入语法,打包器会自动处理依赖与路径:

var worker = new Worker(
  new URL('./workers/aggregate.worker.js', import.meta.url),
  { type: 'module' }
);

worker.onmessage = function (e) {
  renderCells(e.data.cells);
};

worker.postMessage({ id: 1, points: points, cellSize: 24 });

确实需要在 Worker 里画图时,用 OffscreenCanvas 转交控制权, 不要试图在 Worker 里创建 DOM canvas:

// 主线程
var canvas = document.getElementById('map');
var offscreen = canvas.transferControlToOffscreen();
worker.postMessage({ canvas: offscreen }, [offscreen]);

// worker.js
self.onmessage = function (e) {
  var ctx = e.data.canvas.getContext('2d');
  ctx.fillStyle = '#1c7ed6';
  ctx.fillRect(10, 10, 40, 40);
};

验证

Worker 启动时不再出现 document is not defined。 主线程收到聚合结果,数量与主线程直接计算一致。 计算期间拖动地图,主线程帧率保持在 50 fps 以上。

边界要清楚:Worker 适合纯数据计算,不适合持有地图对象。 需要在地图上绘制时,把结果传回主线程,或走 OffscreenCanvas, 不要让地图库进入 Worker。

引用本文

经纬志(2026)。《逻辑挪进 Worker,一加载就报 document 未定义》。https://www.jingweizhi.com/cookbook/webworker-dom-library.html(访问日期 2026-11-03)。