我最近想出了
Web Workers API 。 不幸的是,我之前没有花时间使用这个功能强大的工具。 现代Web应用程序对主要JavaScript执行线程的功能要求很高。 这影响了项目的生产率及其确保便利的用户体验的能力。 网络工作者正是这些天可以帮助开发人员创建快速便捷的Web项目的工具。

我了解一切的那一刻
网络工作者具有许多积极的品质。 但是,当我遇到某个应用程序使用多个DOM事件侦听器的情况时,我才真正意识到它们的用处。 例如表单提交,窗口大小调整,单击按钮等事件。 所有这些侦听器都必须在主线程中工作。 如果主线程因需要花费很长时间才能完成的某些操作而过载,则这将对事件侦听器对用户影响的反应速度产生不良影响。 应用程序“慢下来”,事件正在等待主线程的释放。
我必须承认,事件侦听器之所以引起我极大的兴趣,是因为我最初误解了网络工作者要解决的任务。 起初,我认为它们可以帮助提高代码执行速度。 我认为,如果某个应用程序的代码片段可以在单独的线程中并行执行,那么它可以在一定时间内完成更多工作。 但是在执行Web项目代码期间,情况很普遍,即在开始做某事之前,您需要等待一些事件。 假设仅在完成一些计算后才需要更新DOM。 知道这一点,我天真地相信,无论如何我都必须等待,这意味着将某些代码的执行转移到单独的线程是没有意义的。
这是您可以在此处回忆的代码示例:
const calculateResultsButton = document.getElementById('calculateResultsButton'); const openMenuButton = document.getElementById('#openMenuButton'); const resultBox = document.getElementById('resultBox'); calculateResultsButton.addEventListener('click', (e) => {
在完成一些计算之后,我在这里更新了字段中的文本,大概是冗长的。 在单独的线程中运行此代码似乎毫无意义,因为DOM在此代码完成之前不会更新。 结果,我当然认为该代码需要同步执行。 但是,看到这样的代码,起初我不明白,只要主线程被阻塞,其他事件监听器就不会启动。 这意味着“刹车”开始出现在页面上。
如何“放慢”页面
这是一个证明以上
内容的 CodePen项目。
一个项目,演示页面缓慢的情况按下“
Freeze按钮会使应用程序开始解决同步任务。 所有这一切需要3秒钟(它模拟了冗长的计算的性能)。 如果同时单击“
Increment按钮,则在经过3秒之前,“
Click Count字段中的值将不会更新。 仅在三秒钟后,与
Increment点击次数相对应的新值才会写入此字段。 暂停期间主流被阻止。 结果,应用程序窗口中的所有内容看起来都不起作用。 应用程序界面被冻结。 在“冻结”过程中发生的事件正在等待机会使用主流资源。
如果单击“
Freeze并尝试
resize me! ,然后,直到经过三秒钟,该字段的大小才会改变。 然后,字段的大小将发生变化,但是无需讨论界面中的任何“平滑度”。
事件侦听器是一个比乍看起来大得多的现象
任何用户都不希望使用行为如上例所示的网站。 但是这里只使用了几个事件监听器。 在现实世界中,我们正在谈论完全不同的尺度。 我决定在Chrome中使用
getEventListeners方法,并使用以下脚本来确定附加到各个页面DOM元素的事件侦听器的数量。 该脚本可以直接在开发人员工具控制台中运行。 这是:
Array .from([document, ...document.querySelectorAll('*')]) .reduce((accumulator, node) => { let listeners = getEventListeners(node); for (let property in listeners) { accumulator = accumulator + listeners[property].length } return accumulator; }, 0);
我在不同页面上运行了该脚本,并发现了在其上使用的事件侦听器的数量。 下表显示了我的实验结果。
您不能注意特定的数字。 这里的主要问题是我们正在谈论大量的事件侦听器。 结果,如果应用程序中至少一项长时间运行的操作出错,所有这些侦听器将停止响应用户的影响。 这为开发人员提供了许多使他们的应用程序用户不满意的方法。
摆脱网络工作者的“刹车”
鉴于以上所有内容,让我们重写前面的示例。
这是他的新版本。 它看起来完全像旧的,但内部布置不同。 即,现在用于阻塞主线程的操作已移至其自己的线程。 如果您对本示例所做的操作与上一个示例相同,则可能会发现严重的积极差异。 即,如果在单击“
Freeze按钮之后单击“
Increment ,则“
Click Count字段将被更新(在Web worker完成后,无论如何,数字1都会添加到“
Click Count值中)。
resize me!的
resize me!一样
resize me! 。 在单独的线程中运行的代码不会阻止事件侦听器。 即使执行先前只是简单地“冻结”页面的操作,这也可以使页面的所有元素保持可操作状态。
这是此示例的JS代码:
const button1 = document.getElementById('button1'); const button2 = document.getElementById('button2'); const count = document.getElementById('count'); const workerScript = ` function pause(ms) { let time = new Date(); while ((new Date()) - time <= ms) {} } self.onmessage = function(e) { pause(e.data); self.postMessage('Process complete!'); } `; const blob = new Blob([ workerScript, ], {type: "text/javascipt"}); const worker = new Worker(window.URL.createObjectURL(blob)); const bumpCount = () => { count.innerText = Number(count.innerText) + 1; } worker.onmessage = function(e) { console.log(e.data); bumpCount(); } button1.addEventListener('click', async function () { worker.postMessage(3000); }); button2.addEventListener('click', function () { bumpCount(); });
如果稍微研究一下此代码,您会注意到,尽管Web Workers API可以安排得更舒适,但使用它并没有什么特别可怕的。 由于这是一个简单,快速编写的演示,因此该代码可能看起来很吓人。 为了提高API的可用性并使其更易于使用Web Worker,可以使用一些其他工具。 例如,以下内容对我来说似乎很有趣:
总结
如果您的Web应用程序是一个典型的现代项目,则意味着它很可能具有许多事件侦听器。 它也有可能在主线程中执行许多计算,而这些计算可以在其他线程中完全执行。 结果,您可以为事件的用户和侦听者提供良好的服务,将“繁重的”计算委托给网络工作者。
值得注意的是,对Web工作人员的过度热情以及与Web工作人员的用户界面不直接相关的所有内容的删除可能都不是最好的主意。 对应用程序的这种处理可能需要大量的时间和精力,项目代码将变得更加复杂,并且这种转换的好处将非常小。 相反,可能值得一开始的方法是寻找一个真正的“繁重”代码并将其发布给网络工作者。 随着时间的流逝,使用网络工作者的想法将变得更加熟悉,即使在界面设计阶段,您也可能会受到它的指导。
尽管如此,建议您了解Web Workers API。 该技术享有非常广泛的浏览器支持,并且现代Web应用程序对性能的要求正在不断提高。 因此,我们没有理由拒绝研究网络工作者之类的工具。
亲爱的读者们! 您在项目中使用网络工作者吗?
