JavaScript的秘密生活:克隆
Source: Dev.to
如何使用 Web Workers 保护主线程并防止 UI 冻结。
Timothy 点击了 Export Report 按钮。出现了加载旋转图标,但它完全卡住了。整个浏览器窗口变得无响应。十秒后 UI 解冻,文件下载完成。
“它能工作,” Timothy 说,“但是在处理数据时,整个应用程序会完全死掉。”
Margaret 拉起一把椅子。“Timothy,你已经建好了一个漂亮的厨房,但你只有一位厨师。如果让他去切一万颗洋葱,他就没办法同时去迎接顾客了。”
单线程
Margaret 打开了 Performance(性能)面板,并指向时间轴上占据大块空间的实心黄色块。
“JavaScript 是单线程的,”她解释道。“我们称它为 Main Thread,但你应该把它当作 UI 线程。它的主要职责是绘制屏幕、运行动画以及监听点击。”
Timothy 指着他的代码说:“但是我用了 async 和 await!我以为这样就不会阻塞了。”
“
async用于等待,”Margaret 纠正道。“当你await一个网络请求时,厨师把汤放在炉子上,然后去做别的事。但数据处理——比如格式化巨大的 CSV 或进行大量计算——是主动工作。厨师在切菜,不能离开。”
克隆
“我该如何处理数据而不让屏幕卡死?” Timothy问道。
“你雇一个副厨,” Margaret说。“你创建一个Web Worker。”
Margaret添加了一个新文件 worker.js:
// worker.js – The Sous‑Chef's Room
self.onmessage = function (event) {
const rawData = event.data;
// The chef is chopping the onions in the background
const processedCSV = heavyDataProcessing(rawData);
// Send the finished product back to the kitchen
self.postMessage(processedCSV);
};
Web Worker 是 JavaScript 引擎的字面克隆,与原始线程并行运行。它无法访问 DOM 或 UI 元素,但可以使用诸如 fetch() 和 setTimeout() 等 API。
调度
// main.js – The Kitchen (UI Thread)
const exportButton = document.getElementById('export-btn');
exportButton.addEventListener('click', () => {
// 1. Show the spinning UI
showLoadingSpinner();
// 2. Hire the sous‑chef
const worker = new Worker('worker.js');
// 3. Listen for the note back under the door
worker.onmessage = function (event) {
const processedCSV = event.data;
downloadFile(processedCSV);
hideLoadingSpinner();
// Fire the sous‑chef so he doesn't consume memory
worker.terminate();
};
// 4. Handle emergencies in the kitchen
worker.onerror = function (error) {
console.error('Sous‑chef had a breakdown:', error);
hideLoadingSpinner();
showErrorMessage();
worker.terminate();
};
// 5. Slide the raw data under the door
const rawData = getMassiveDataset();
worker.postMessage(rawData);
});
再次运行代码时,Timothy 看到加载指示器平稳旋转,能够与其他 UI 元素交互,并在十秒后收到文件下载,整个过程没有任何卡顿。
架构分歧
“这改变了一切,”Timothy 在观看流畅的动画时评论道。
“这迫使你以不同的方式思考,”Margaret 同意道。“初级开发者把所有东西都放在主线程上,并希望计算机足够快以隐藏它。高级开发者不惜一切代价保护主线程,将其视为纯粹的呈现层。如果一个任务的纯 CPU 时间超过约 50 ms,他们就会把它交给 Worker。”
高级技巧:可转移对象
postMessage 会创建数据的 副本。对于大规模数据集,复制可能代价高昂。使用 可转移对象(例如 ArrayBuffer)将内存所有权转移给 worker,而无需复制,从而实现零开销的即时交接。
旋转器
有了工作线程,旋转器可以自由旋转,而主线程保持响应——迎接顾客,接听电话,让场所保持活力——而副厨师在后厨悄悄切洋葱。