JavaScript 性能
JavaScript 性能优化
Section titled “JavaScript 性能优化”编写高效的 JavaScript 代码对于创建快速响应的 Web 应用程序至关重要。本节介绍优化 JavaScript 性能的关键策略。
1. 减少循环中的活动
Section titled “1. 减少循环中的活动”循环很常见,循环内部的任何低效率都会因迭代次数而放大。通过将计算或属性访问移出循环来优化,如果它们不随每次迭代而改变。
低效示例(重复访问 arr.length):
// Bad: arr.length is accessed in each iteration// 不好:每次迭代都访问 arr.length// const arr = new Array(1000).fill(0);// for (let i = 0; i < arr.length; i++) {// // Do something// // 执行一些操作// }更高效(缓存 arr.length):
const arr = new Array(1000).fill(0);const len = arr.length; // Cache the length// 缓存长度for (let i = 0; i < len; i++) { // Do something // 执行一些操作}// Modern JavaScript engines are often smart enough to optimize this automatically,// 现代 JavaScript 引擎通常足够智能,可以自动优化这一点,// but for very large arrays or performance-critical code, caching can still be beneficial.// 但对于非常大的数组或性能关键的代码,缓存仍然可能是有益的。2. 减少 DOM 访问和操作
Section titled “2. 减少 DOM 访问和操作”访问和操作文档对象模型 (DOM) 比其他 JavaScript 操作慢得多。最小化 DOM 交互:
- 缓存 DOM 元素: 如果需要多次访问同一个 DOM 元素,只需查询一次并将其存储在变量中。
- 批量更新 DOM: 尝试通过更少的操作来进行更改,而不是对 DOM 进行许多小更改。例如,在内存中构建 HTML 字符串或 DocumentFragment,然后一次性添加到 DOM 中。
- 避免布局抖动 (Layout Thrashing): 在循环中重复读取布局属性(如
offsetHeight或getComputedStyle)然后写入 DOM 会强制浏览器多次重新计算布局,这非常耗费性能。如果可能,请分组读取和写入操作。
缓存 DOM 元素的示例:
// Inefficient:// 低效:// document.getElementById("myElement").innerText = "First update";// document.getElementById("myElement").style.color = "blue";
// Efficient:// 高效:// const myElement = document.getElementById("myElement");// if (myElement) {// myElement.innerText = "First update";// myElement.style.color = "blue";// }使用 DocumentFragment 进行批量更新:
// const list = document.getElementById("myList");// const items = ["Apple", "Banana", "Cherry"];// const fragment = document.createDocumentFragment();// items.forEach(itemText => {// const li = document.createElement("li");// li.textContent = itemText;// fragment.appendChild(li);// });// list.appendChild(fragment); // Single append operation to the live DOM// list.appendChild(fragment); // 单次添加到实际 DOM 中的操作3. 保持 DOM 树结构小巧
Section titled “3. 保持 DOM 树结构小巧”大型 DOM 树(许多 HTML 元素)会降低页面渲染、CSS 样式应用以及 DOM 操作的速度。力求精简的 DOM 结构。
这不仅改善了初始页面加载时间,还使得后续的 DOM 查询和操作更快。
4. 优化变量使用(需权衡)
Section titled “4. 优化变量使用(需权衡)”以前的建议是“如果你不打算保存值,就不要创建新变量。”例如:
// Old style:// 旧风格:// const firstName = "John";// const lastName = "Doe";// const fullName = firstName + " " + lastName;// document.getElementById("demo").innerHTML = fullName;可以写成:
// const firstName = "John";// const lastName = "Doe";// document.getElementById("demo").innerHTML = firstName + " " + lastName;然而,现代 JavaScript 引擎非常擅长优化变量分配。优先考虑代码的可读性和可维护性。命名良好的中间变量可以使代码更容易理解。仅在性能分析表明某个关键路径存在瓶颈时,才进行微观优化变量使用。
5. 高效的 JavaScript 加载策略
Section titled “5. 高效的 JavaScript 加载策略”JavaScript 的加载方式和时机对页面加载性能和感知速度有显著影响。
- 将脚本放在
<body>底部: 这允许浏览器先解析和渲染 HTML 内容,然后才遇到阻塞页面解析的<script>标签。 - 使用
async属性:<script async src="script.js"></script>异步下载脚本(不阻塞 HTML 解析),并在下载完成后立即执行。如果存在多个带有async的脚本,执行顺序不保证。适用于分析脚本等独立脚本。 - 使用
defer属性:<script defer src="script.js"></script>异步下载脚本,但在 HTML 文档完全解析后,在DOMContentLoaded事件之前,按顺序执行。这通常是非关键脚本的最佳选择,它们需要 DOM 准备就绪并应按特定顺序执行。 - 动态加载脚本: 当需要时以编程方式加载脚本,例如在用户交互后或当页面某个部分变得可见时。
动态加载示例:
// function loadMyScript() {// const script = document.createElement("script");// script.src = "myHeavyScript.js";// document.body.appendChild(script);// }// // Call loadMyScript() when appropriate, e.g., on a button click.// // 在适当的时候调用 loadMyScript(),例如在按钮点击时。6. 避免使用 with
Section titled “6. 避免使用 with”with 关键字会扩展作用域链,这使得 JavaScript 引擎难以优化代码,并可能导致变量解析的歧义。它还会对性能产生负面影响。
with 在严格模式下被禁止,并且通常被认为是坏习惯。完全避免使用它。
7. 其他性能考虑
Section titled “7. 其他性能考虑”- 使用高效的算法和数据结构: 为任务选择正确的工具。例如,对于某些操作(如频繁添加/删除或检查是否存在),
Map或Set可能比普通对象或数组性能更好。 - 防抖 (Debouncing) 和 节流 (Throttling): 对于频繁触发的事件处理程序(例如
scroll、resize、input),使用防抖(在一定时间没有活动后才执行函数)或节流(在指定时间间隔内最多执行一次函数)来限制调用次数。 - 记忆化 (Memoization): 缓存耗时函数调用的结果,并在输入相同时返回缓存的结果。
- Web Workers: 对于不需要访问 DOM 的 CPU 密集型任务,将其卸载到 Web Workers 中在单独的线程中运行,以保持主 UI 线程响应。
- 代码分割 (Code Splitting) 和 Tree Shaking: 对于大型应用程序,使用支持代码分割(将代码分割成按需加载的小块)和 Tree Shaking(从最终打包中移除未使用的代码)的打包工具(如 Webpack、Rollup、Parcel)。
- 对代码进行性能分析 (Profiling): 使用浏览器开发者工具(Performance 选项卡)识别 JavaScript 中的瓶颈。不要过早优化;专注于实际慢的区域。