Web前端入门第 86 问:JavaScript 中的 Web Worker 为什么能提升代码性能?
<p>最初的 JS 执行代码都是一条线执行到底,当遇到比较耗时的操作时,比如大数组循环运算,就会导致页面卡着,就像假死一样。就像一个人在厨房烧菜一样,需要依次完成切菜、炒菜、装盘这些步骤,此过程中没办法同时做其他事情,必须按顺序执行每一个步骤。</p><p><code>Web Worker</code> 赋予了 JS 分配任务的能力,在遇到复杂的计算型任务时,比如 <code>canvas</code> 图形图像处理(添加滤镜、矩阵变换等),此类不依赖 DOM 操作的计算型任务都可以交由Web Worker 来处理,这样不会阻塞主线程的任务调度,从而提升前端的代码运行速度。</p>
<h2 id="任务时序图">任务时序图</h2>
<p><img src="https://img2024.cnblogs.com/blog/596097/202509/596097-20250902095416926-1690607120.png"></p>
<h2 id="模拟耗时任务">模拟耗时任务</h2>
<p>看如下代码,使用一个超大的 for 循环,模拟 JS 中的耗时任务,让代码执行时主线程卡顿,还原假死现象:</p>
<pre><code class="language-html"><div id="output"></div>
<button id="start">开始复杂任务</button>
<script>
(() => {
let times = 0
const output = document.getElementById('output')
function loop () {
setTimeout(() => {
times ++
output.innerText = times
loop()
}, 1000);
}
loop()
document.getElementById('start').addEventListener('click', () => {
let n = 0
console.time('任务耗时')
for (let i = 0; i < 10000000000; i++) {
n += i
}
console.timeEnd('任务耗时')
})
})();
</script>
</code></pre>
<p>执行结果:</p>
<p><img src="https://img2024.cnblogs.com/blog/596097/202509/596097-20250902095422651-1546553422.gif"></p>
<p>可以看到点击 <strong>开始复杂任务</strong> 按钮时,在计时器的第 <code>4</code> 秒主线程卡主了将近 4 秒,然后再恢复运行,这就是单线程中的 JS 耗时任务导致的页面假死现象。</p>
<h2 id="使用-web-worker-解决耗时问题">使用 Web Worker 解决耗时问题</h2>
<p>看了上面的耗时任务导致页面假死,再使用 Web Worker 来重写一下上面代码:</p>
<p><strong>main.html</strong></p>
<pre><code class="language-html"><div id="output"></div>
<button id="start">开始复杂任务</button>
<script>
(() => {
let times = 0
const output = document.getElementById('output')
function loop () {
setTimeout(() => {
times ++
output.innerText = times
loop()
}, 1000);
}
loop()
const worker = new Worker('./worker.js')
worker.onmessage = event => {
// 子线程计算结果
console.log(event.data)
console.timeEnd('任务耗时')
}
worker.onerror = event => {
console.error('Worker 异常:', event)
}
document.getElementById('start').addEventListener('click', () => {
console.time('任务耗时')
worker.postMessage(10000000000)
})
})();
</script>
</code></pre>
<p><strong>worker.js</strong></p>
<pre><code class="language-js">// worker.js
self.onmessage = event => {
let n = 0
let max = event.data
for (let i = 0; i < max; i++) {
n += i
}
postMessage(n)
}
</code></pre>
<p>执行结果:</p>
<p><img src="https://img2024.cnblogs.com/blog/596097/202509/596097-20250902095428494-1922477401.gif"></p>
<p>可以看到虽然任务耗时长短差不多,但是主线程在点击按钮之后并没有进入假死状态,定时器还是在顺利执行,所以 Web Worker 中运行的复杂任务并不会影响主线程的任务调度。</p>
<h3 id="web-worker-限制">Web Worker 限制</h3>
<p>在子线程中运行的代码,无法直接操作 DOM,无法访问 window/document 对象,也无法使用 localStorage 等,如果使用这些 API,代码将会报错:</p>
<p><img src="https://img2024.cnblogs.com/blog/596097/202509/596097-20250902095434379-1684909870.jpg"></p>
<h2 id="for-循环优化">for 循环优化</h2>
<p>注意上述代码中的 <code>max</code> 变量,为什么需要一个变量来保存 <code>event.data</code> 值?而不是直接使用 <code>event.data</code> 循环?将 worker.js 改造一下,看看不同使用方式的任务耗时:</p>
<pre><code class="language-js">// worker.js
self.onmessage = event => {
console.time('max 循环耗时')
let n = 0
let max = event.data
for (let i = 0; i < max; i++) {
n += i
}
console.timeEnd('max 循环耗时')
console.time('Object 循环耗时')
let m = 0
for (let i = 0; i < event.data; i++) {
m += i
}
console.timeEnd('Object 循环耗时')
postMessage(n)
}
</code></pre>
<p><strong>main.html</strong></p>
<pre><code class="language-js">// main.html
(() => {
const worker = new Worker('./worker.js')
document.getElementById('start').addEventListener('click', () => {
worker.postMessage(100000000)
})
})();
</code></pre>
<p>执行结果:</p>
<p><img src="https://img2024.cnblogs.com/blog/596097/202509/596097-20250902095439368-91783066.jpg"></p>
<p>可以明显看到,性能耗时相差将近 6 倍,这数字会随着对象属性越多,耗时越长!!<strong>所以在循环中应当尽量避免读取对象属性,尽可能使用变量来做循环条件!!</strong></p>
<h2 id="写在最后">写在最后</h2>
<p>可以使用 Web Worker 同时启用多个工作线程,只是在任务调度的时候,需要注意响应结果的先后顺序是否对主线程的运行有影响。</p>
<p>一些复杂的计算任务(比如视频转码,图片压缩,图片处理等),都丢给子线程处理吧,咱们前端也可以玩玩多线程~~</p>
</div>
<div id="MySignature" role="contentinfo">
<p> </p>
<p style="font-size: 18px;font-weight: bold;">文章首发于微信公众号【<span style="color:rgb(255, 71, 87)">前端路引</span>】,欢迎 <span style="color:#4ec259">微信扫一扫</span> 查看更多文章。</p>
<p>
<img style="max-width: 320px;" src="https://images.cnblogs.com/cnblogs_com/linx/2447020/o_250228035031_%E5%85%AC%E4%BC%97%E5%8F%B7%E4%BA%8C%E7%BB%B4%E7%A0%81.png"/>
</p>
<p>本文来自博客园,作者:前端路引,转载请注明原文链接:https://www.cnblogs.com/linx/p/19069469</p>
<p> </p><br><br>
来源:https://www.cnblogs.com/linx/p/19069469
頁:
[1]