在javascript中,我们应该尽可能的用局部变量来代替全局变量,这句话所有人都知道,可是这句话是谁先说的?为什么要这么做?有什么根据么?不这么做,对性能到底能带来多大的损失?本文就来探讨这些问题的答案,从根本上了解变量的读写性能都和哪些因素有关。
著作权声明
本文译自 nicholas c. zakas 于2009年2月10日在个人网站上发表的《javascript variable performance》。原文是唯一的正式版,本文是经过原作者(nicholas c. zakas)授权的简体中文翻译版(simplified chinese translation)。译者(明达)在翻译的准确性上做了大量的努力,并承诺译文的内容完全忠于原文,但可能还是包含疏漏和不妥之处,欢迎大家指正。译注的内容是非正式的,仅代表译者个人观点。
以下是对原文的翻译:
在如何提高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)。作用域链会通过下面两个步骤来建立,用于进行标识符解析。
在执行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功能的。
上面的结果是通过对变量执行写操作而得出的,其实我很好奇,读取变量时的情况会不会有什么不同,于是接着做了下面的测试。结果发现,读的速度要比写的速度快一些,但是性能变化的趋势是一致的。
和上个测试一样,internet explorer和firefox还是最慢的,opera表现了非常抢眼的性能,而同样的,chrome和最新版本的webkit午夜版显示了和作用域深度无关的性能趋势,同样需要注意的是,firefox 3.1 beta 2的变量访问时间还是会伴随着深度出现一个奇怪的跳跃。
在测试的过程中,我发现一个有趣的现象,就是chrome在访问全局变量的时候会有额外的性能损失。访问全局变量的时间和作用域层数没有关系,但是会比访问同样层数的局部变量的时间多出50%。
这两个测试可以给我们带来什么启示呢?首先是验证了那个古老的观点,就是要尽可能的使用局部变量。在所有的浏览器下,访问局部变量都比访问跨作用域的变量要快,当然也包括全局变量。下面这几点应该是通过这个测试得出的经验吧:
如果你想围绕这个话题展开更多的讨论,我在上个月的 mountain view javascript meetup 中曾经发表了一个小演讲。可以在slideshare上下载 幻灯片,或者观看聚会的 完整视频,我的演讲大概从11分钟左右时开始。
译者笔记
大家如果在阅读本文的过程中,有什么疑惑,建议延伸阅读以下两篇文章:
新闻热点
疑难解答