在 JavaScript 中,直接定义一个对象(Object),性能上远不如定义一个 JSON.parse()
的表达式。具体来说,下面的两行,JSON.parse
的表达式会有更好的性能表现:
const slow = { foo: 42, bar: 1337 };
const fast = JSON.parse('{"foo":42,"bar":1337}');
同样的效果,但是在 JavaScript 引擎中的表现却差别很大。根据这里给出的测试数据,JSON.parse
的速度是直接写对象速度的 1.7 倍。而且这不仅仅只是 V8 表现上的不同,在各类 JavaScript 引擎上都有类似的表现,性能差异均非常明显(JavaScriptCore 的性能差异可以到两倍)。
这里差异的主要原因在于,引擎在解析时候算法复杂度有着巨大的差异。简单来说,JSON 的数据结构是非常简单且固定的,因而在解析的时候可以有更好的表现。这种简单体现在以下几个方面:
- JSON 的数据支持类型不多,只有字符串,数组,数字,NULL,对象这几种;相比之下,JavaScript 中一个对象的支持类型非常的复杂,情况更多;
- 从抽象语法树(AST)的角度看,
JSON.parse
的情况比单纯写一个 JavaScript 对象要简单的多。对于前者来说,就是一个 CallExpression 和一个 StringLiteral;而对于一个 JavaScript 对象来说,涉及到大量的 ObjectExpression,当中可能还包含 StringLiteral,NumericLiteral,Identifier 等等; - JSON 的解析是上下文无关的;而 JavaScript 对象的解析却需要结合当前的上下文(context)来确定;
举一个例子来说明:假设有这样一个 JavaScript 代码片段:
const x = 1;
const y = ({ x }
这里的 x
代表什么含义,其实有非常多的可能性,比如:
const y = ({ x })
,此时x
的值和上下文中的x
变量是相关的,定义是一个 JavaScript 对象;const y = ({ x } = { x: 2 })
,此时x
和上下文是相关的,但定义的是一个赋值语句,而不是对象(根据语法,对const
二次赋值导致语法错误);const y = ({ x }) => x;
,此时x
的值和上面的x
无关,是一个函数的参数;
换句话说,当 JavaScript 引擎在解析一个 JavaScript 对象的时候,需要考虑很多的可能性,在解析的过程中很可能无法确定当前的类型,甚至连语法是否正确也不能确定。但反观 JSON,定义就简单的多,在解析的当下,引擎就可以很清楚的知道当前的内容是一个数组,还是一个对象,亦或是有语法错误。
除了上述提到的性能比较数据之外,这里还有一份针对 Redux 应用的优化分析。数据显示,使用 JSON.parse
调用之后 TTI (Time To Interactive) 时间缩短了 0.74s (18%)。考虑到整个改动是非常“简单”的,这一性能提升显得非常客观。
这里之所以说改动是非常“简单”的,是因为整个优化思路非常的明确,完全可以通过对应的工具在编译时完成。目前开源社区已经提供了各类相关的工具,可以直接使用,列举一些如下:
- Webpack(v4.35.3 或以上)默认会将 JSON 打包成
JSON.parse()
;使用json-loader
可以去掉这一优化(具体见这个 Pull Request); - 一些 Babel Plugin 支持将满足要求的 JavaScript 对象转化成
JSON.parse
语法,比如 babel-plugin-object-to-json-parse 或 babel-plugin-transform-optimize-object-literal。