主要观点:
- 一般来说 JavaScript 代码运行速度比应有的慢,因为未得到适当优化,介绍了常见优化技巧。
- 优化的权衡往往是可读性,何时追求性能与可读性的问题留给读者。
- 优化必然涉及基准测试,微优化可能因实际情况而无意义。
关键信息和重要细节:
- 避免工作(Avoid work):优化时首先应避免不必要的工作,如在 React 中应用
memo()
等,包括 memoization、laziness 和 incremental computation 等概念,具体应用依上下文而定。 - 避免字符串比较(Avoid string comparisons):Javascript 中
===
比较字符串看似简单,但实际会逐个字符比较,成本较高。应避免字符串作为枚举,如在 TypeScript 中默认枚举为整数可避免。通过对比字符串比较和整数比较的代码示例,展示了差异,在字符串密集型代码中影响较大,如使 JSON5 解析器速度提升 2 倍。 - 避免不同形状(Avoid different shapes):Javascript 引擎通过假设对象形状来优化代码,若函数接收不同形状的对象,性能会大幅下降。V8 有单态(1 种形状)、多态(2 - 4 种形状)和巨态(5 + 种形状)3 种模式。建议创建所有对象时具有相同形状,甚至在 React 组件中改变 props 顺序也可能触发此问题。
- 避免数组/对象方法(Avoid array/object methods):在 JavaScript 中,函数式编程通常比命令式编程慢,如
map
、filter
、reduce
等数组方法和Object.values()
等对象方法会创建数组副本,循环次数多,而for
循环可一次循环。 - 避免间接性(Avoid indirection):优化可从间接性来源入手,如代理对象(Proxy objects)较难优化,访问对象的
.
或[]
操作也是间接性,函数调用也有成本,引擎一般会内联函数,但不能保证。代理基准测试在 V8 中表现不佳,访问深层嵌套对象和直接访问的性能差异也受引擎优化影响。 - 避免缓存未命中(Avoid cache misses):从 CPU 角度看,从 RAM 中检索内存较慢,CPU 主要使用预取(prefetching)和 L1/L2/L3 缓存优化。预取时按顺序访问数据很关键,避免随机访问内存。可利用此知识优化数据操作,如将对象转换为
TypedArray
实例。L1/L2/L3 缓存优化时,应使用尽可能少的数据保持工作数据集在快速缓存中,如分割工作为小块。 - 避免大对象(Avoid large objects):当对象形状过大,引擎会使用常规哈希表,缓存未命中会显著降低性能。应避免频繁索引大对象,可将对象预先转换为数组,组织数据使 ID 在模型上,避免使用
Object.keys()
等方法。 - 使用 eval:某些 Javascript 模式难以被引擎优化,使用
eval()
或其衍生物可消除这些模式的成本,如避免创建具有动态对象键的对象,可在编译过滤谓词函数时丢弃不会执行的分支。但要注意eval()
的安全问题,如不要信任用户输入等。 - 谨慎使用字符串(Use strings, carefully):字符串操作在 JavaScript 中很常见,引擎会根据使用情况用 C++表示
String
对象。字符串连接+
不会创建输入字符串的副本,但字符串切片也可能不会创建副本,然而一旦开始修改字节,就会产生复制成本。应尽量避免字符串的修改方法,如.trim()
等,字符串模板在某些引擎中可能比+
慢,使用时需基准测试。 - 使用特化(Use specialization):在性能优化中,特化是重要概念,根据特定用例调整逻辑,如对于通常为空的标签,可专门优化函数以提高性能,但条件改变时特化可能适得其反。
- 数据结构(Data structures):使用错误的数据结构对性能影响比上述优化更大,应熟悉
Map
、Set
等原生数据结构,以及链表、优先队列、树(RB 和 B+)和 trie 等。通过比较Array.includes
和Set.has
的示例,展示了数据结构选择的重要性。 - 基准测试(Benchmarking):基准测试是优化中最重要但也最难的部分,应优先优化占运行时间最大的部分,避免微基准测试,在生产模式下进行优化,怀疑优化结果和工具的准确性,根据相关引擎进行基准测试。
- profiling & tools:在浏览器中进行 profiling 时,应使用干净的浏览器配置文件,避免浏览器扩展影响测量,浏览器 profiling 工具是基于样本的,可能会低估小但频繁的函数调用。除了常规浏览器 devtools,还有一些可用的工具,如 Chrome devtools 的实验标志、deoptexplorer-vscode 扩展、编译调试 shell 等可帮助理解代码性能。
总结:本文全面介绍了 JavaScript 代码优化的各个方面,包括避免不必要的工作、优化常见操作、选择合适的数据结构等,并强调了基准测试和 profiling 工具的重要性。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。