JavaScript 引擎内部:浏览器如何让你的代码焕发生机
我一直对驱动现代 Web 应用的深层抽象着迷。但随着我逐渐了解 JavaScript 的底层执行机制,我开始对一个更深层次的主题感兴趣——JavaScript 引擎;这些强大的运行时在浏览器中运行时,会悄悄地为 JavaScript 代码注入活力,并为你执行所有这些优化。最近,我一直在研究它们的实现和历史,并想重点介绍一些最有趣的内容。
这篇文章介绍了 JavaScript 引擎如何从解析代码到使其发挥最大性能,引擎构建中使用的常见设计模式(通常可以暗示为什么某些代码比其他代码表现更好),以及我们可以从引擎构建同行那里借鉴的其他一般技巧,以编写更好的 JS。
什么是 JavaScript 引擎?
从高层次上讲,你可以说 JavaScript 引擎是一个程序,它接收你的 JavaScript (JS) 代码并将其转换为计算机可以实际运行的内容。每个浏览器都有自己的 JavaScript 引擎,旨在为用户提供最佳性能:
- V8:为 Google Chrome 和 Node.js 提供支持。
- SpiderMonkey:Mozilla Firefox 的引擎。
- JavaScriptCore:Safari 使用。
- Chakra:Microsoft Edge 的原始引擎(采用 Chromium 之前)。
这些引擎使您的代码能够运行,并能在各种不同的设备和平台上良好运行。
以下是在浏览器中运行 JavaScript 代码时发生的情况,一步一步:
- 解析:引擎首先读取(“解析”)你提供的 JavaScript 代码,并将其转换为计算机可以执行的一组指令。然后,这组指令被转换成所谓的“抽象语法树”。
例如:阅读食谱并将其分解为原料和步骤。
- 编译:现代引擎不会直接从 AST 运行代码,而是将其编译为字节码(较低级别的表示)甚至机器码,以便可以更快地执行。
主要特性:在运行时引擎中,使用即时 (JIT) 编译来优化代码。这意味着它们将调整运行时执行的二进制代码,以使其运行速度更快。
- 执行:编译完成后,优化后的机器码会在硬件上执行,引擎会持续优化。它会对程序中频繁执行的部分(称为热路径)应用许多高级优化技术,使其运行速度非常快。
JavaScript 引擎中的关键优化
现代引擎使用多种技术来确保 JavaScript 运行速度快且内存高效:
即时 (JIT) 编译
JIT 编译是指引擎在程序执行时会编译一些代码。这意味着引擎对程序的实际运行情况有更深入的了解,因此可以对其进行优化。
示例:如果重复执行循环,引擎会将其识别为热路径并对其进行优化以加快执行速度。
内联缓存
在访问对象属性的过程中,引擎首先检查缓存位置,如果找到,则会减少用于查找属性实际所在位置的总体执行时间。
示例:这基本上意味着如果您多次访问 user.name,引擎将记住存储用户数据的对象,以便下次不必再次找到该对象而可以直接访问它。
垃圾收集
JavaScript 引擎有一个叫做“垃圾收集器”的功能。它本质上是一个程序,当它确定某个对象不再可用或无法访问时,就会释放内存。
示例:如果您在函数中创建了一个对象,并且在该函数完成后,您不再需要它,则引擎会看到该情况并自动将其从内存中删除。
浏览器如何使用 JavaScript 引擎
JavaScript 引擎与浏览器的渲染引擎同时运行。当您访问网站时:
- 浏览器首先会获取 HTML、CSS 和 JavaScript 文件。
- 浏览器中的 JavaScript 引擎将解析并执行这些脚本。
- 渲染引擎将处理过的HTML、CSS、JavaScript组合起来显示网页。
例子:
当您打开任何电子商务网站时,诸如将商品添加到购物车、更新价格、加载产品详细信息等动态功能都由 JavaScript 引擎处理,并且根本不会发生页面刷新。
编写引擎友好型代码的技巧
引擎友好的代码不仅能帮你编写高效的应用程序,还能提升应用程序的可扩展性和可维护性。这里有一个可操作的列表,其中包含一些解释和示例,可以帮助你充分发挥 JavaScript 引擎的潜力。
1. 优化 DOM 操作
频繁且不必要的 DOM 操作会降低应用程序的速度,因为每次更新都会强制浏览器重新计算样式并重新渲染元素。
最佳实践:
通过批量处理来尽量减少直接 DOM 更新。
使用文档片段或像 React 这样的框架来高效处理更新。
例如:而不是:
使用文档片段:
为什么它有帮助:通过减少 DOM 操作的数量,您可以最大限度地减少回流和重绘,从而使您的应用程序运行得更快。
2. 避免循环和条件中的深度嵌套
引擎难以处理深度嵌套结构,这会使您的代码更难优化和调试。
最佳实践:
将深度嵌套的循环或条件重构为更小的可重用函数。
例如:而不是:
使用:
为什么有帮助:减少嵌套可以提高可读性并允许引擎更有效地优化代码。
3. 循环中的缓存长度
每次在循环中使用 array.length 时,引擎都会重新计算长度。这可能会导致大型数组的性能问题。
最佳实践:
在循环之前缓存数组长度。
例如:而不是:
使用:
为什么它有帮助:通过缓存长度,您可以避免冗余计算,从而使循环运行得更快。
4. 使用现代语法和内置方法
与旧技术相比,现代 JavaScript 功能和方法通常会得到引擎的进一步优化。
最佳实践:
- 使用 let 和 const 代替 var 来声明变量。
- 在适用的情况下,使用 map、filter 和 reduce 等数组方法代替手动迭代。
例如:而不是:
使用:
为什么它有帮助:内置方法针对性能进行了高度优化,使您的代码更加简洁、易读。
5. 通过适当的清理避免内存泄漏
当不再需要的对象未被释放时,就会发生内存泄漏,从而导致内存使用量随着时间的推移而增加。
最佳实践:
- 当不再需要事件监听器时将其删除。
- 避免不必要地创建全局变量。
例如:而不是:
使用:
为什么有帮助:适当的清理可确保释放未使用的资源,防止性能随着时间的推移而下降。
6. 对重资产使用延迟加载
仅在需要时加载图像、脚本和数据等资源,以减少应用程序的初始加载时间。
最佳实践:
- 对图像使用loading="lazy"属性。
- 在需要时动态导入 JavaScript 模块。
例子:
为什么有帮助:延迟加载通过推迟非必要资产的加载来提高性能。
7.避免阻塞主线程
JavaScript 在单个线程上运行,因此长时间运行的操作可能会阻塞 UI 并使您的应用程序无响应。
最佳实践:
对于大量计算,请使用 setTimeout、setInterval 或 Web Workers。
例如:而不是:
使用 Web Worker:
帮助原因:卸载繁重的任务可防止 UI 冻结,确保流畅的用户体验。
8. 使用内联缓存
JavaScript 引擎通过缓存对象的位置来优化频繁访问的对象属性。确保对象具有一致的结构以获得更好的性能。
最佳实践:
避免动态添加或删除对象的属性。
例如:而不是:
使用:
为什么它有帮助:引擎优化具有可预测结构的对象,从而加快属性访问。
9. 尽量减少冗余计算,
当结果可以缓存时,避免多次执行相同的计算。
最佳实践:
对于昂贵的函数调用使用记忆法。
例如:而不是:
使用:
为什么它有帮助:记忆化减少了冗余计算,提高了递归函数的性能。
10. 使用 DevTools 进行分析和优化
使用浏览器的开发人员工具查看代码的哪些部分使其速度变慢。
最佳实践:
-定期分析您的应用程序以查看哪部分代码运行缓慢。-
根据实际使用模式进行优化。
示例:使用 Chrome DevTools 的“性能”选项卡来识别缓慢的脚本或过多的重排并相应地调整代码。
为什么它有帮助:分析将让您获得由数据驱动的洞察力,并让您了解应该将优化重点放在哪个部分。
示例:V8 在 Google Chrome 中的作用
Google 的 V8 引擎是目前最先进的 JavaScript 引擎之一。它为 Chrome 和 Node.js 提供支持,使 Gmail、Google 地图或 YouTube 等现代 Web 应用即使在较慢的机器上也能快速高效地运行。V8 应用了许多性能优化,例如 JIT 编译和内联缓存,以实现这一目标。
JavaScript 引擎的演变
JavaScript 引擎自诞生以来已经走过了漫长的道路!
- 早期:在早期,引擎直接解释代码,这使得执行速度较慢。
- JIT 的介绍:后来,引擎开始应用 JIT 编译技术来获得更高的运行时性能。
- 当前创新:现代引擎利用 WebAssembly(Wasm)集成和多线程执行等先进技术来实现无与伦比的性能。
结论:
作为开发者,我们花费大量时间编写代码,但了解 JavaScript 引擎如何执行这些代码可以帮助我们编写更好、更优化的应用程序。了解 JavaScript 编译器的各种功能(例如 JIT 编译、垃圾回收或内联缓存)后,您就可以利用这些优势来设计解决方案。
希望这篇文章能帮助你理解在浏览器中运行 JavaScript 时屏幕背后的魔力。请继续编写更简洁高效的代码,并感谢这些引擎为使我们的应用程序充满活力所付出的卓越努力。
祝你编程愉快!为构建更快更流畅的网络干杯!
鏂囩珷鏉ユ簮锛�https://dev.to/mukhilpadmanabhan/inside-javascript-engines-how-browsers-bring-your-code-to-life-h1