本文深入探讨JavaScript中局部变量优于全局变量的底层原理。通过解析作用域链和标识符搜索机制,结合多浏览器基准测试,量化了不同作用域深度对变量读写性能的具体影响,揭示了V8等现代引擎的优化策略及传统浏览器的性能瓶颈。
局部变量性能优化的理论基础
为何要优先使用局部变量而非全局变量?其理论依据何在?若忽略这一原则,对程序性能究竟会造成多大程度的损耗?本文将深入剖析这些问题的答案,从底层机制厘清变量读写性能受哪些关键因素影响。
著作权声明
本文译自
Nicholas C. Zakas于2009年2月10日在个人网站上发表的《
JavaScript Variable Performance》。原文为唯一正式版本,本译文经原作者(Nicholas C. Zakas)授权发布,为简体中文版(Simplified Chinese Translation)。译者(明达)在确保翻译准确性方面付出了大量努力,并承诺译文内容完全忠实于原文,但可能仍存在疏漏或不妥之处,欢迎读者指正。译注部分属非正式内容,仅代表译者个人观点。
JavaScript作用域链与标识符解析机制
以下是对原文的翻译: 在提升JavaScript性能的建议中,最常被提及的一条便是尽量使用局部变量(local variables)来替代全局变量(global variables)。在我从事Web开发的九年时间里,这一建议始终被反复强调且从未受到质疑,其理论基础源于JavaScript对作用域(scoping)和标识符解析(identifier resolution)的处理方式。
首先需明确,函数在JavaScript中本质上表现为对象,创建函数的过程即等同于创建一个对象。每个函数对象都包含一个名为[[Scope]]的内部属性,该属性记录了函数创建时的作用域信息。实际上,[[Scope]]属性对应的是一个对象(Variable Objects)列表,列表中的对象可从函数内部访问。例如,若定义一个全局函数A,则A的[[Scope]]内部属性仅包含一个全局对象(Global Object);若在A内部创建新函数B,则B的[[Scope]]属性将包含两个对象:位于列表前端的是函数A的Activation Object对象,后端则是全局对象(Global Object)。
当函数被执行时,系统会自动创建一个可执行对象(Execution Object),并绑定一个作用域链(Scope Chain)。作用域链通过以下两个步骤建立,以支持标识符解析:
- 首先,将函数对象[[Scope]]内部属性中的对象按顺序复制到作用域链中。
- 其次,在函数执行期间,会创建一个新的Activation Object对象,该对象包含this、参数(arguments)以及局部变量(包括命名参数)的定义。此Activation Object对象将被置于作用域链的最前端。
在执行JavaScript代码时,若遇到标识符,系统会根据标识符名称在执行上下文(Execution Context)的作用域链中进行搜索。搜索从作用域链的第一个对象(即该函数的Activation Object对象)开始;若未找到,则继续搜索作用域链中的下一个对象,直至找到标识符的定义。若在搜索完作用域中的最后一个对象(即全局对象Global Object)后仍未找到,系统将抛出错误,提示变量未定义(undefined)。这是ECMA-262标准中描述的函数执行模型和标识符解析(Identifier Resolution)过程,事实证明,大部分JavaScript引擎确实遵循此实现方式。需注意,ECMA-262并未强制要求采用此结构,仅对此功能进行了描述。
不同作用域深度的性能测试分析
理解标识符解析(Identifier Resolution)的过程后,我们便能明白为何局部变量的解析速度优于其他作用域的变量,这主要得益于搜索过程的大幅缩短。然而,具体能快多少?为回答此问题,我模拟了一系列测试,以评估不同作用域深度中变量的性能表现。
第一个测试是向变量写入最简单的值(此处使用字面量数值1),结果如图所示,颇具趣味性:
从结果中可以看出,当标识符解析需要进行深度搜索时,会伴随性能损失,且性能损失的幅度随标识符深度的增加而递增。意料之中,Internet Explorer的表现最差(但公平地说,IE 8已有所改善)。值得注意的是,存在例外情况:Google Chrome和最新的WebKit午夜版在访问变量时时间保持相对稳定,不随作用域深度增加而增长。这应归功于它们所使用的下一代JavaScript引擎:V8和SquirrelFish。这些引擎在执行代码时进行了优化,显然,这些优化使变量访问速度比以往更快。Opera的表现也相当不错,比IE、Firefox和当前版本的Safari快得多,但慢于基于V8和SquirrelFish的浏览器。Firefox 3.1 Beta 2的表现略显出人意料:对局部变量的执行效率非常高,但随着作用域层数增加,效率便大打折扣。需注意,此处使用的均为默认设置,即Firefox未开启Trace功能。
2下一页阅读全文