手机版

为什么在JavaScript中尽可能多地使用局部变量?

时间:2021-10-26 来源:互联网 编辑:宝哥软件园 浏览:

如果不这样做,会带来多大的性能损失?本文将讨论这些问题的答案,从根本上了解哪些因素与变量的读写表现有关。版权声明本文翻译自尼古拉斯c扎卡斯于2009年2月10日在其个人网站上发布的《JavaScript Variable Performance》。是原文的唯一官方版本,本文为原作者(尼古拉斯c扎卡斯)授权的简体中文译本。译者(明达)在翻译的准确性上下了很大功夫,并承诺翻译的内容完全忠实于原文,但仍可能有遗漏和不足之处。请指正。翻译笔记的内容是非正式的,仅代表译者的个人观点。以下是原文的翻译:关于如何提高JavaScript的性能,最常听到的建议是尽可能使用局部变量而不是全局变量。在我九年的Web开发过程中,这个建议一直在我的耳朵里,从来没有被质疑过,这个建议的基础来自于JavaScript的作用域和标识符解析的方法。首先要明确函数在JavaScript中体现为对象,创建函数的过程其实就是创建对象的过程。每个函数对象都有一个名为[[作用域]]的内部属性,该属性包含创建函数时的作用域信息。实际上,[[范围]]属性对应于一个变量对象列表,列表中的对象可以从函数内部访问。比如我们设置一个全局函数A,A的[[作用域]]的内部属性只包含一个全局对象,而如果我们在A中创建新的函数B,B的[[作用域]]的属性包含两个对象,函数A的激活对象在前,全局对象在后。当一个函数被执行时,它会自动创建一个执行对象,同时绑定一个范围链。范围链将通过以下两个标识符解析步骤来建立。1.首先,将函数对象[[Scope]]内部属性中的对象依次复制到范围链中。其次,在执行函数时,会创建一个新的Activation Object,它包含了这个、参数和局部变量(包括命名参数)的定义,这个Activation Object会放在作用域链的前面。在执行JavaScript代码的过程中,遇到标识符时,会根据标识符的名称在执行上下文的范围链中进行搜索。从作用域链的第一个对象(函数的Activation Object对象)开始,如果没有找到,搜索作用域链中的下一个对象,以此类推,直到找到标识符的定义。如果搜索后没有找到作用域中的最后一个对象,即全局对象,将引发错误,提示用户变量未定义。这是ECMA-262标准中描述的函数执行模型和标识符解析的过程。事实证明,大多数JavaScript引擎都是这样实现的。需要注意的是,ECMA-262并不需要这种结构,只是描述了这部分功能。了解了标识符解析的过程后,我们就能明白为什么局部变量的解析速度比其他范围的变量要快,主要是因为搜索过程大大缩短了。但是会有多快呢?为了回答这个问题,我模拟了一系列测试来测试变量在不同范围深度的性能。第一个测试是将最简单的值写入变量(这里使用的是文字值1)。结果如下图所示,很有意思:

从结果中不难看出,当标识符解析过程需要深度搜索时,会伴随着性能损失,性能损失的程度会随着标识符深度的增加而增加。不出所料,Internet Explorer的表现最差(但公平地说,IE 8仍有一些改进)。值得注意的是,这里也有一些例外。谷歌Chrome和最新的WebKit午夜版保持了访问变量的稳定时间,不会随着范围深度的增加而增加。当然,这应该归功于他们使用的下一代JavaScript引擎,V8和SquirrelFish。这些引擎在执行代码时进行了优化,显然,这些优化使访问变量比以前更快。Opera也表现不错,比IE、火狐和当前版本的Safari快很多,但比基于V8和Squirrelfish的浏览器慢。火狐3.1 Beta 2的性能有点出乎意料,执行局部变量的效率很高,但是随着范围内层数的增加,效率大大降低。需要注意的是,我这里使用的所有设置都是默认设置,这意味着火狐没有启动Trace功能。上面的结果是通过写变量得到的。其实我很好奇读变量的时候情况是否不同,所以做了以下测试。发现读的速度比写的速度快,但表现变化的趋势是一致的。

和上次测试一样,Internet Explorer和Firefox仍然是最慢的,Opera表现非常抢眼。同样,Chrome和最新版本的Webkit midnight显示的性能趋势与范围的深度无关。还需要注意的是,火狐3.1 Beta 2的可变访问时间会随着深度进行奇怪的跳跃。在测试过程中,我发现了一个有趣的现象,那就是Chrome在访问全局变量时会失去额外的性能。访问全局变量的时间与作用域的层数无关,但会比访问相同层数的局部变量的时间长50%。这两次考试能给我们带来什么启示?首先,它验证了应该尽可能多地使用局部变量的旧思想。在所有浏览器中,访问局部变量比访问跨范围变量(包括全局变量)更快。以下几点应该是从这次测试中获得的经验:*仔细检查函数中使用的所有变量。如果一个变量不是由当前作用域定义的,并且被多次使用,那么我们应该将这个变量保存在一个局部变量中,并使用这个局部变量进行读写。这可以帮助我们将超出范围的变量的搜索深度减少到1。这对于全局变量尤其重要,因为全局变量总是放在范围链的最后位置进行搜索。*避免使用with语句。因为它将修改执行上下文的范围链,并在前面添加一个变量对象。这意味着在用执行的过程中,实际的局部变量都移动到了作用域链上的第二个位置,这会带来性能损失。*如果您确定一段代码将引发异常,则应避免使用try-catch,因为catch分支在作用域链上的处理方式与使用相同。但是,try分支的代码没有性能损失,因此建议使用try-catch来捕获那些不可预测的错误。如果你想围绕这个话题进行更多的讨论,我上个月在山景JavaScript Meetup做了一个简短的演讲。您可以从SlideShare下载幻灯片或观看聚会的完整视频。我的演讲大约11分钟后开始。译者注如果您在阅读本文时有任何疑问,建议您阅读以下两篇文章:* 《JavaScript对象模型-执行模型》 * 《ECMA-262第三版》由Richie撰写,主要看第10章,这是执行上下文,其中详细解释了本文提到的名词。最后,Nicholas提到了一个Mountain View JavaScript Meetup,其实是各种现实世界活动的组织网站,需要翻墙才能访问。住在加州真开心,有那么多好的活动可以参加,呵呵。

版权声明:为什么在JavaScript中尽可能多地使用局部变量?是由宝哥软件园云端程序自动收集整理而来。如果本文侵犯了你的权益,请联系本站底部QQ或者邮箱删除。