Appearance
WebWorker 澶氱嚎绋嬪浘褰㈠鐞?
鍦ㄥ疄闄呯殑鍥惧舰缂栬緫鍣ㄥ紑鍙戜腑锛屼綘鍙兘閬囧埌杩囪繖鏍风殑闂锛氬綋鎵ц澶嶆潅鐨勫浘鍍忔护闀滃鐞嗐€佸ぇ閲忕煝閲忓浘褰㈡爡鏍煎寲鎴栭珮鍒嗚鲸鐜囩敾甯冩覆鏌撴椂锛屾暣涓晫闈細闄峰叆鍗¢】锛岀敤鎴锋棤娉曟嫋鎷姐€佺偣鍑伙紝鐢氳嚦杩炲姩鐢婚兘鍋滄浜嗐€傝繖鏄洜涓?JavaScript 杩愯鍦ㄥ崟绾跨▼鐜锛屾墍鏈夎绠椼€佹覆鏌撱€佷簨浠跺搷搴旈兘鍦ㄤ富绾跨▼鎺掗槦鎵ц锛屼竴鏃︽煇涓换鍔¤€楁椂杩囬暱锛屽氨浼氶樆濉炴暣涓簲鐢ㄣ€?
閭d箞锛岃兘涓嶈兘鎶婅繖浜涜€楁椂鐨勫浘褰㈠鐞嗕换鍔?澶栧寘"缁欏叾浠栫嚎绋嬶紝璁╀富绾跨▼涓撴敞浜庣敤鎴蜂氦浜掑憿锛熺瓟妗堟槸锛氬彲浠ワ紝杩欏氨鏄?Web Worker 鐨勪环鍊笺€?
鏈珷灏嗕粠瀹為檯闂鍑哄彂锛岄€愭瑙g瓟浠ヤ笅鏍稿績闂锛?
- 涓荤嚎绋嬩笌 Worker 濡備綍閫氫俊锛熷浣曡璁℃秷鎭崗璁紵
- 濡備綍鍦?Worker 涓覆鏌?Canvas锛圤ffscreenCanvas锛夛紵
- 濡備綍楂樻晥浼犺緭鍥惧儚鏁版嵁锛堝彲杞Щ瀵硅薄锛夛紵
- 濡備綍瀹炵幇鍒嗙墖娓叉煋涓庣粨鏋滃悎骞讹紵
- 浣曟椂閫傚悎浣跨敤 Worker锛屼綍鏃朵笉閫傚悎锛?
涓荤嚎绋嬩笌 Worker锛氭秷鎭┍鍔ㄧ殑骞惰妯″瀷
棣栧厛瑕侀棶涓€涓棶棰橈細Worker 涓庝富绾跨▼鏄粈涔堝叧绯伙紵
Worker 鏄繍琛屽湪鐙珛绾跨▼涓殑 JavaScript 鎵ц鐜銆傚畠涓庝富绾跨▼鏃犳硶鍏变韩鍙橀噺銆佹棤娉曠洿鎺ヨ闂?DOM锛屽敮涓€鐨勯€氫俊鏂瑰紡鏄?*娑堟伅浼犻€?*锛坄postMessage 鍜?onmessage`锛夈€傝繖绉嶉殧绂讳繚璇佷簡绾跨▼瀹夊叏锛屼絾涔熷甫鏉ヤ簡璁捐涓婄殑绾︽潫锛?
- **涓荤嚎绋?*锛氳礋璐?UI 娓叉煋銆佷簨浠跺搷搴斻€丏OM 鎿嶄綔
- Worker锛氳礋璐g函璁$畻浠诲姟锛堝鍥惧儚澶勭悊銆佹暟鎹浆鎹€佸鏉傛覆鏌擄級
- 閫氫俊锛氶€氳繃寮傛娑堟伅浼犻€掓寚浠ゅ拰鏁版嵁
杩欑妯″紡绫讳技浜?澶栧寘宸ュ巶"锛氫富绾跨▼鏄敳鏂癸紝鍙戦€佷换鍔℃寚浠ゅ拰鍘熸潗鏂欙紱Worker 鏄箼鏂癸紝鎺ュ崟銆佸姞宸ャ€佷氦浠樻垚鍝併€?
浣曟椂閫傚悎浣跨敤 Worker锛?
骞堕潪鎵€鏈変换鍔¢兘閫傚悎 Worker銆傚垽鏂爣鍑嗘槸锛?
閫傚悎锛?
- 閲?CPU 杩愮畻锛堝婊ら暅銆佸ぇ瑙勬ā鍍忕礌澶勭悊銆佺煝閲忔爡鏍煎寲锛?
- 鍙媶鍒嗙殑骞惰浠诲姟锛堝鍒嗙墖娓叉煋銆佹壒閲忚浆鎹級
- 棰勮绠楁垨鍚庡彴澶勭悊锛堝缂撳瓨鐢熸垚銆佹暟鎹帇缂╋級
涓嶉€傚悎锛?
- 棰戠箒涓?DOM 浜や簰锛圵orker 鏃犳硶璁块棶 DOM锛?
- 杞婚噺绾ц绠楋紙閫氫俊寮€閿€澶т簬璁$畻鏈韩锛?
- 闇€瑕佺珛鍗冲悓姝ョ粨鏋滅殑鍦烘櫙锛圵orker 閫氫俊鏄紓姝ョ殑锛?
鎬濊€冧竴涓嬶細濡傛灉浣犵殑浠诲姟鏄?姣忓抚鏇存柊 10 涓璞$殑浣嶇疆"锛岃繖鏄交閲忕骇璁$畻锛屼笉闇€瑕?Worker锛涗絾濡傛灉鏄?瀵逛竴寮?4K 鍥惧儚搴旂敤楂樻柉妯$硦"锛岃繖灏辨槸鍏稿瀷鐨?Worker 閫傜敤鍦烘櫙銆?
鍩虹閫氫俊锛氭瀯寤烘竻鏅扮殑娑堟伅鍗忚
鐜板湪鎴戣闂浜屼釜闂锛?濡備綍璁捐涓荤嚎绋嬩笌 Worker 鐨勯€氫俊鍗忚锛?
涓€涓仴澹殑閫氫俊鍗忚鑷冲皯闇€瑕佸寘鍚細
- 鍛戒护鏍囪瘑锛坄cmd`锛夛細鍛婅瘔 Worker 瑕佸仛浠€涔?
- 璇锋眰 ID锛坄reqId`锛夛細鍖归厤璇锋眰鍜屽搷搴旓紙鏀寔骞跺彂澶氳姹傦級
- 鏁版嵁杞借嵎锛坄payload`锛夛細浠诲姟鎵€闇€鐨勫弬鏁?
- 鍝嶅簲閫氶亾锛氭垚鍔熺粨鏋滄垨閿欒淇℃伅
涓嬮潰鏄竴涓渶灏忓彲鐢ㄧ殑鍗忚瀹炵幇锛?
*涓荤嚎绋嬶細鍒涘缓 Worker 骞跺皝瑁呰姹傚嚱鏁?
javascript
// main.js
const worker = new Worker('renderer.js', { type: 'module' });
// 灏佽 Promise 椋庢牸鐨勮姹傚嚱鏁?
function request(cmd, payload) {
return new Promise((resolve, reject) => {
const reqId = crypto.randomUUID(); // 鐢熸垚鍞竴璇锋眰 ID
// 鐩戝惉 Worker 杩斿洖鐨勬秷鎭?
const onMessage = (event) => {
const { type, reqId: id, data, error } = event.data;
if (id !== reqId) return; // 蹇界暐涓嶅尮閰嶇殑鍝嶅簲
worker.removeEventListener('message', onMessage);
if (type === 'error') {
reject(new Error(error));
} else {
resolve(data);
}
};
worker.addEventListener('message', onMessage);
worker.postMessage({ cmd, reqId, payload });
});
}
// 浣跨敤绀轰緥
async function init() {
try {
await request('init', { width: 800, height: 600 });
console.log('Worker 鍒濆鍖栨垚鍔?);
const result = await request('draw', { shapes: [...] });
console.log('缁樺埗瀹屾垚', result);
} catch (error) {
console.error('Worker 閿欒:', error);
}
}**鍏抽敭璁捐鐐?*锛?
- 璇锋眰 ID锛氭敮鎸佸苟鍙戝涓姹傦紝閬垮厤鍝嶅簲閿欎贡
- Promise 灏佽锛氬皢寮傛娑堟伅杞崲涓烘洿鏄撶敤鐨?async/await 椋庢牸
- 閿欒閫氶亾锛歐orker 鍙戠敓閿欒鏃惰兘姝g‘浼犲洖涓荤嚎绋?
*Worker 绔細鎺ユ敹鍛戒护骞跺洖澶?
javascript
// renderer.js
self.onmessage = async (event) => {
const { cmd, reqId, payload } = event.data;
try {
if (cmd === 'init') {
// 鍒濆鍖栭€昏緫
const { width, height } = payload;
// ...
reply(reqId, { status: 'ok' });
} else if (cmd === 'draw') {
// 缁樺埗閫昏緫
const { shapes } = payload;
// ...
reply(reqId, { rendered: shapes.length });
} else {
replyError(reqId, `鏈煡鍛戒护: ${cmd}`);
}
} catch (error) {
replyError(reqId, error.message);
}
};
function reply(reqId, data) {
self.postMessage({ type: 'result', reqId, data });
}
function replyError(reqId, error) {
self.postMessage({ type: 'error', reqId, error });
}杩欎釜鍗忚宸茬粡瓒冲搴斿澶ч儴鍒嗗満鏅€備絾鏄畠杩樻湁涓€涓棶棰橈細**Worker 濡備綍娓叉煋 Canvas锛?*涓荤嚎绋嬬殑 Canvas 鏃犳硶鐩存帴浼犵粰 Worker锛屽洜涓?DOM 瀵硅薄涓嶅彲浼犻€掋€傝繖灏卞紩鍑轰簡涓嬩竴涓牳蹇冩蹇碉細OffscreenCanvas銆?
OffscreenCanvas锛氬湪 Worker 涓覆鏌撶敾甯?
鐜板湪鎴戣闂涓変釜闂锛?Worker 鏃犳硶璁块棶 DOM锛屽浣曟覆鏌?Canvas锛?
绛旀鏄細OffscreenCanvas鈥斺€斾竴涓劚绂?DOM 鐨?Canvas 鎺ュ彛锛屽彲浠ュ湪 Worker 涓娇鐢ㄣ€?
OffscreenCanvas 鐨勪袱绉嶅垱寤烘柟寮?
*鏂瑰紡涓€锛氫富绾跨▼杞Щ鎺у埗鏉冿紙鎺ㄨ崘锛?
javascript
// main.js
const canvas = document.querySelector('#myCanvas');
const offscreen = canvas.transferControlToOffscreen(); // 杞Щ鎺у埗鏉?
// 灏?OffscreenCanvas 浣滀负鍙浆绉诲璞′紶缁?Worker
worker.postMessage({ cmd: 'init', canvas: offscreen }, [offscreen]);閲嶈锛氳皟鐢?transferControlToOffscreen() 鍚庯紝涓荤嚎绋?*澶卞幓浜嗗 Canvas 鐨勭粯鍒舵帶鍒?*锛屾墍鏈夌粯鍒跺繀椤诲湪 Worker 涓畬鎴愩€傝繖鏄竴绉?鎵€鏈夋潈杞Щ"锛屼笉鏄鍒躲€?
鏂瑰紡浜岋細Worker 鍐呴儴鐩存帴鍒涘缓
javascript
// renderer.js
const offscreen = new OffscreenCanvas(800, 600);
const ctx = offscreen.getContext('2d');杩欑鏂瑰紡閫傚悎绾悗鍙版覆鏌擄紙濡傜敓鎴愮紦瀛樺浘鍍忥級锛屼笉闇€瑕佹樉绀哄湪椤甸潰涓娿€?
鍦?Worker 涓幏鍙栦笂涓嬫枃骞剁粯鍒?
javascript
// renderer.js
let ctx, width, height;
self.onmessage = async (event) => {
const { cmd, reqId, payload, canvas } = event.data;
try {
if (cmd === 'init') {
ctx = canvas.getContext('2d'); // 鑾峰彇 2D 涓婁笅鏂?
width = canvas.width;
height = canvas.height;
// 缁樺埗涓€涓祴璇曡儗鏅?
ctx.fillStyle = '#1a1a1a';
ctx.fillRect(0, 0, width, height);
reply(reqId, { status: 'initialized' });
}
if (cmd === 'draw') {
const { shapes } = payload;
renderShapes(shapes);
reply(reqId, { rendered: shapes.length });
}
if (cmd === 'resize') {
width = payload.width;
height = payload.height;
ctx.canvas.width = width;
ctx.canvas.height = height;
reply(reqId, { status: 'resized' });
}
} catch (error) {
replyError(reqId, error.message);
}
};
function renderShapes(shapes) {
ctx.clearRect(0, 0, width, height);
shapes.forEach(shape => {
ctx.save();
ctx.translate(shape.x, shape.y);
ctx.rotate(shape.rotation || 0);
ctx.fillStyle = shape.fill || '#09f';
ctx.fillRect(-shape.width / 2, -shape.height / 2, shape.width, shape.height);
ctx.restore();
});
}
function reply(reqId, data) {
self.postMessage({ type: 'result', reqId, data });
}
function replyError(reqId, error) {
self.postMessage({ type: 'error', reqId, error });
}**鍏抽敭鐐?*锛?
- OffscreenCanvas 鐨勭粯鍒?API 涓庢櫘閫?Canvas 瀹屽叏涓€鑷?
- 涓荤嚎绋嬬湅鍒扮殑娓叉煋缁撴灉鏄?*鑷姩鍚屾**鐨勶紙鏃犻渶鎵嬪姩鍥炰紶锛?
- 璋冩暣灏哄鏃堕渶瑕佸湪 Worker 涓缃?
canvas.width鍜?canvas.height
鏈夋病鏈夊緢绁炲鐨勬劅瑙夛紵涓荤嚎绋嬬湅鍒扮殑 Canvas 鍐呭锛屽疄闄呮槸鍦?Worker 涓粯鍒剁殑锛屾暣涓繃绋嬩笉浼氶樆濉炵敤鎴蜂氦浜掋€?
娴忚鍣ㄦ敮鎸佷笌闄嶇骇鏂规
**鍏煎鎬х幇鐘?*锛?024骞达級锛?
- 鉁?Chrome/Edge锛氬畬鏁存敮鎸?
- 鉁?Firefox锛氬畬鏁存敮鎸?
- 鈿狅笍 Safari锛氶儴鍒嗘敮鎸侊紙闇€妫€鏌ョ増鏈級
闄嶇骇绛栫暐锛?
javascript
// main.js
function initRenderer(canvas) {
if ('transferControlToOffscreen' in canvas && window.Worker) {
// 鏀寔 OffscreenCanvas锛屼娇鐢?Worker
const offscreen = canvas.transferControlToOffscreen();
const worker = new Worker('renderer.js', { type: 'module' });
worker.postMessage({ cmd: 'init', canvas: offscreen }, [offscreen]);
return { type: 'worker', instance: worker };
} else {
// 涓嶆敮鎸侊紝闄嶇骇鍒颁富绾跨▼娓叉煋
const ctx = canvas.getContext('2d');
return { type: 'main', ctx };
}
}闄嶇骇鏂规纭繚浜嗗湪涓嶆敮鎸佺殑鐜涓嬶紝搴旂敤浠嶇劧鍙敤锛屽彧鏄€ц兘绋嶅樊銆?
鍙浆绉诲璞★細闆舵嫹璐濅紶杈撳浘鍍忔暟鎹?
鐜板湪鎴戣闂鍥涗釜闂锛?濡備綍楂樻晥浼犺緭鍥惧儚鏁版嵁锛?
榛樿鎯呭喌涓嬶紝閫氳繃 postMessage 浼犻€掔殑瀵硅薄浼氳**缁撴瀯鍖栧厠闅?*锛堟繁鎷疯礉锛夛紝杩欏湪浼犺緭澶ч噺鍍忕礌鏁版嵁鏃朵細甯︽潵宸ㄥぇ寮€閿€銆備緥濡傦紝涓€寮?1920脳1080 鐨?RGBA 鍥惧儚闇€瑕佺害 8MB 鍐呭瓨锛屾繁鎷疯礉浼氬鑷存€ц兘鏄捐憲涓嬮檷銆?
瑙e喅鏂规鏄娇鐢?*鍙浆绉诲璞?*锛圱ransferable Objects锛夛紝瀹炵幇**闆舵嫹璐濅紶杈?*銆?
鍙浆绉诲璞$殑宸ヤ綔鍘熺悊
鍙浆绉诲璞$殑"鎵€鏈夋潈"浠庡彂閫佹柟杞Щ鍒版帴鏀舵柟锛屽師瀵硅薄鍙樹负"detached"锛堜笉鍙敤锛夛紝閬垮厤浜嗗鍒跺紑閿€銆?
鏀寔鐨勫彲杞Щ瀵硅薄绫诲瀷锛?
ArrayBuffer锛堝師濮嬪瓧鑺傛暟缁勶級ImageBitmap锛堜綅鍥惧浘鍍忥級MessagePortOffscreenCanvas
绀轰緥 1锛氫娇鐢?ImageBitmap 浼犺緭鍥惧儚
javascript
// main.js
async function sendImageToWorker(canvas) {
// 浠?Canvas 鍒涘缓 ImageBitmap锛堥浂鎷疯礉锛?
const bitmap = await createImageBitmap(canvas);
// 浣滀负鍙浆绉诲璞′紶閫掔粰 Worker
worker.postMessage({ cmd: 'processImage', image: bitmap }, [bitmap]);
// 娉ㄦ剰锛歜itmap 鐜板湪宸蹭笉鍙敤锛堣杞Щ锛?
console.log(bitmap.width); // 閿欒锛欳annot read properties of detached object
}javascript
// renderer.js
self.onmessage = async (event) => {
const { cmd, reqId, image } = event.data;
if (cmd === 'processImage') {
// 鍦?OffscreenCanvas 涓婄粯鍒跺苟澶勭悊
const canvas = new OffscreenCanvas(image.width, image.height);
const ctx = canvas.getContext('2d');
ctx.drawImage(image, 0, 0);
// 搴旂敤婊ら暅...
applyFilter(ctx);
// 杩斿洖缁撴灉锛堝悓鏍蜂綔涓?ImageBitmap 杞Щ锛?
const result = canvas.transferToImageBitmap();
self.postMessage({ type: 'result', reqId, data: result }, [result]);
}
};**鍏抽敭鐐?*锛?
transferToImageBitmap()鏂规硶灏?OffscreenCanvas 鐨勫唴瀹硅浆鎹负 ImageBitmap锛屽悓鏃?娓呯┖鐢诲竷*- 閫氳繃
postMessage鐨勭浜屼釜鍙傛暟浼犻€掑彲杞Щ瀵硅薄鏁扮粍 - 浼犺緭鍚庡師瀵硅薄绔嬪嵆澶辨晥
绀轰緥 2锛氫娇鐢?ArrayBuffer 浼犺緭鍍忕礌鏁版嵁
javascript
// main.js
const canvas = document.querySelector('#canvas');
const ctx = canvas.getContext('2d');
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
// imageData.data 鏄?Uint8ClampedArray锛屽簳灞傛槸 ArrayBuffer
const buffer = imageData.data.buffer;
worker.postMessage({
cmd: 'processPixels',
buffer,
width: canvas.width,
height: canvas.height
}, [buffer]); // 杞Щ buffer 鎵€鏈夋潈
// 鐜板湪 buffer 宸茶 detached
console.log(imageData.data.length); // 0锛堝凡鏃犳暟鎹級javascript
// renderer.js
self.onmessage = (event) => {
const { cmd, reqId, buffer, width, height } = event.data;
if (cmd === 'processPixels') {
const pixels = new Uint8ClampedArray(buffer);
// 鐩存帴鎿嶄綔鍍忕礌鏁扮粍锛堝鍙嶈壊锛?
for (let i = 0; i < pixels.length; i += 4) {
pixels[i] = 255 - pixels[i]; // R
pixels[i + 1] = 255 - pixels[i + 1]; // G
pixels[i + 2] = 255 - pixels[i + 2]; // B
// pixels[i + 3] 鏄?Alpha锛屼繚鎸佷笉鍙?
}
// 杩斿洖澶勭悊鍚庣殑 buffer
self.postMessage({ type: 'result', reqId, buffer }, [buffer]);
}
};javascript
// main.js锛堟帴鏀剁粨鏋滐級
worker.addEventListener('message', (event) => {
const { type, reqId, buffer } = event.data;
if (type === 'result') {
const pixels = new Uint8ClampedArray(buffer);
const imageData = new ImageData(pixels, width, height);
ctx.putImageData(imageData, 0, 0);
}
});鎬ц兘瀵规瘮锛?
- 缁撴瀯鍖栧厠闅嗭紙榛樿锛夛細闇€瑕佹繁鎷疯礉鏁翠釜鏁版嵁锛岃€楁椂涓庢暟鎹ぇ灏忔垚姝f瘮
- 鍙浆绉诲璞★細浠呰浆绉绘墍鏈夋潈鎸囬拡锛岃€楁椂鍑犱箮涓洪浂锛圤(1)锛?
瀵逛簬澶у昂瀵稿浘鍍忓鐞嗭紝鍙浆绉诲璞$殑鎬ц兘浼樺娍闈炲父鏄庢樉銆?
本章小结
本章介绍了 Web Worker 与 Canvas 的基础结合:
核心概念:
- Web Worker 并行模型与适用场景
- 消息协议设计
- OffscreenCanvas API 使用
零拷贝传输:
- 可转移对象(Transferable Objects)
- ImageBitmap 和 ArrayBuffer 传输
- 性能提升 10-100倍
浏览器兼容性:
- 特性检测与降级方案
- Chrome/Edge 全支持
- Firefox 部分支持
在下一章,我们将学习多 Worker 并行加速和典型应用场景。