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


Posted in Javascript onApril 06, 2009

不这么做,对性能到底能带来多大的损失?本文就来探讨这些问题的答案,从根本上了解变量的读写性能都和哪些因素有关。

著作权声明

本文译自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)。作用域链会通过下面两个步骤来建立,用于进行标识符解析。

1. 首先将函数对象[[Scope]]内部属性中的对象,按顺序复制到作用域链中。
2. 其次,在函数执行时,会创建一个新的Activation Object对象,这个对象中包含了this、参数(arguments)、局部变量(包括命名的参数)的定义,这个Activation Object对象会被置于作用域链的最前面。

在执行JavaScript代码的过程中,当遇到一个标识符,就会根据标识符的名称,在执行上下文(Execution Context)的作用域链中进行搜索。从作用域链的第一个对象(该函数的Activation Object对象)开始,如果没有找到,就搜索作用域链中的下一个对象,如此往复,直到找到了标识符的定义。如果在搜索完作用域中的最后一个对象,也就是全局对象(Global Object)以后也没有找到,则会抛出一个错误,提示用户该变量未定义(undefined)。这是在ECMA-262标准中描述的函数执行模型和标识符解析(Identifier Resolution)的过程,事实证明,大部分的JavaScript引擎确实也是这样实现的。需要注意的是,ECMA-262并没有强制要求采用这种结构,只是对这部分功能加以描述而已。

了解标识符解析(Identifier Resolution)的过程以后,我们就能明白为什么局部变量的解析速度要比其他作用域的变量快,主要是由于搜索过程被大幅缩短了。但是,具体会快多少呢?为了回答这个问题,我模拟了一系列的测试,来测试不同作用域深度中变量的性能。

第一个测试是向一个变量中写入一个最简单的值(这里使用字面量的数值1),结果如下图显示,很有趣:

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

从结果中不难看出,当标识符解析的过程需要进行深度搜索时,会伴随性能损失,而且性能损失的程度会随着标识符深度的增加而递增。意料之中的是,Internet Explorer表现的是最差的(但公平的说,IE 8还是有一些改善的)。值得注意的是,这里有一些例外情况,Google Chrome和最新的WebKit午夜版在访问变量的时间保持得很稳定,不会随着作用域深度的递增而增长。当然,这应该归功于它们所使用的下一代 JavaScript引擎,V8和SquirrelFish。这些引擎在执行代码时进行了优化,而且很明显,这些优化使访问变量的速度比以往更快。 Opera表现的也很不错,比IE、Firefox和当前版本的Safari要快的多,但比基于V8和Squirrelfish的浏览器要慢。 Firefox 3.1 Beta 2的表现有点出人意料,对于局部变量执行的效率非常高,但随着作用域层数的增加,效率便大打折扣。需要注意的是,我这里使用的都是默认设置,也就是说 Firefox是没有开启Trace功能的。

上面的结果是通过对变量执行写操作而得出的,其实我很好奇,读取变量时的情况会不会有什么不同,于是接着做了下面的测试。结果发现,读的速度要比写的速度快一些,但是性能变化的趋势是一致的。

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

和上个测试一样,Internet Explorer和Firefox还是最慢的,Opera表现了非常抢眼的性能,而同样的,Chrome和最新版本的Webkit午夜版显示了和作用域深度无关的性能趋势,同样需要注意的是,Firefox 3.1 Beta 2的变量访问时间还是会伴随着深度出现一个奇怪的跳跃。

在测试的过程中,我发现一个有趣的现象,就是Chrome在访问全局变量的时候会有额外的性能损失。访问全局变量的时间和作用域层数没有关系,但是会比访问同样层数的局部变量的时间多出50%。

这两个测试可以给我们带来什么启示呢?首先是验证了那个古老的观点,就是要尽可能的使用局部变量。在所有的浏览器下,访问局部变量都比访问跨作用域的变量要快,当然也包括全局变量。下面这几点应该是通过这个测试得出的经验吧:

* 仔细检查函数中所有使用的变量,如果有一个变量不是当前作用域定义的,而且使用了不止一次,那么我们就应该把这个变量保存在局部变量中,而使用这个局部变量来进行读写操作。这样可以帮助我们将作用域外的变量的搜索深度减少到1.这对全局变量尤为重要,因为全局变量总是被放到作用域链的最后位置来搜索。
* 避免使用with语句。因为它会修改执行上下文(Execution Context)的作用域链,在最前面添加一个对象(Variable Object)。这就意味着在执行with的过程中,实际上的局部变量都被移到作用域链上的第二个位置,这会带来性能上的损失。
* 如果你确定一段代码肯定会抛出异常,那么就要避免使用try-catch,因为catch分支在作用域链上的处理方法和with是一样的。但try分支的代码是没有性能损失的,所以还是建议用try-catch来捕获那些不可预知的错误。

如果你想围绕这个话题展开更多的讨论,我在上个月的Mountain View JavaScript Meetup中曾经发表了一个小演讲。可以在SlideShare上下载幻灯片,或者观看聚会的完整视频,我的演讲大概从11分钟左右时开始。

译者笔记

大家如果在阅读本文的过程中,有什么疑惑,建议延伸阅读以下两篇文章:
* Richie写的《JavaScript对象模型-执行模型
* 《ECMA-262第三版》,主要看看第十章,就是执行上下文(Execution Context)那张,本文提到的名词在那里都有详细的解释。

在最后的时候,Nicholas提到一个Mountain View JavaScript Meetup,Meetup那个网站其实就是一个各种现实世界活动的组织网站,需要翻墙才能访问,住在California真幸福,有那么多的好活动可以参加,呵呵。

Javascript 相关文章推荐
在JavaScript中遭遇级联表达式陷阱
Mar 08 Javascript
用javascript实现改变TEXTAREA滚动条和按钮的颜色,以及怎样让滚动条变得扁平
Apr 20 Javascript
Javascript实现单张图片浏览
Dec 18 Javascript
js老生常谈之this,constructor ,prototype全面解析
Apr 05 Javascript
JavaScript常用代码书写规范的超全面总结
Sep 11 Javascript
jQuery Ajax传值到Servlet出现乱码问题的解决方法
Oct 09 Javascript
JS针对Array的各种操作汇总
Nov 29 Javascript
利用Vue.js实现checkbox的全选反选效果
Jan 18 Javascript
js输入框使用正则表达式校验输入内容的实例
Feb 12 Javascript
浅谈ng-zorro使用心得
Dec 03 Javascript
javascript利用键盘控制小方块的移动
Apr 20 Javascript
关于Vue中的options选项
Mar 22 Vue.js
JavaScript 在各个浏览器中执行的耐性
Apr 06 #Javascript
javascript 获取图片颜色
Apr 05 #Javascript
Mozilla 表达式 __noSuchMethod__
Apr 05 #Javascript
关于javascript document.createDocumentFragment()
Apr 04 #Javascript
HTML 自动伸缩的表格Table js实现
Apr 01 #Javascript
Javascript 原型和继承(Prototypes and Inheritance)
Apr 01 #Javascript
说说掌握JavaScript语言的思想前提想学习js的朋友可以看看
Apr 01 #Javascript
You might like
新手学习PHP的一些基础知识分享
2011/07/27 PHP
PHP用SAX解析XML的实现代码与问题分析
2011/08/22 PHP
PHP文件系统管理(实例讲解)
2017/09/19 PHP
使用JavaScript 实现各种跨域的方法
2013/05/08 Javascript
客户端js判断文件类型和文件大小即限制上传大小
2013/11/20 Javascript
js showModalDialog 弹出对话框的简单实例(子窗体)
2014/01/07 Javascript
JS中完美兼容各大浏览器的scrolltop方法
2015/04/17 Javascript
JS对大量数据进行多重过滤的方法
2016/11/04 Javascript
微信小程序进行微信支付的步骤昂述
2016/12/01 Javascript
JS排序之快速排序详解
2017/04/08 Javascript
javascript实现日期三级联动下拉框选择菜单
2020/12/03 Javascript
vue.js使用v-model指令实现的数据双向绑定功能示例
2018/05/22 Javascript
优雅地使用loading(推荐)
2019/04/20 Javascript
VueJS 取得 URL 参数值的方法
2019/07/19 Javascript
详细教你微信公众号正文页SVG交互开发技巧
2019/07/25 Javascript
vue回到顶部监听滚动事件详解
2019/08/02 Javascript
[59:59]EG vs IG 2018国际邀请赛小组赛BO2 第二场 8.16
2018/08/17 DOTA
Python如何import文件夹下的文件(实现方法)
2017/01/24 Python
Python正则表达式分组概念与用法详解
2017/06/24 Python
用matplotlib画等高线图详解
2017/12/14 Python
python调用并链接MATLAB脚本详解
2019/07/05 Python
利用 Flask 动态展示 Pyecharts 图表数据方法小结
2019/09/04 Python
python带参数打包exe及调用方式
2019/12/21 Python
keras的siamese(孪生网络)实现案例
2020/06/12 Python
Python DES加密实现原理及实例解析
2020/07/17 Python
使用Django的JsonResponse返回数据的实现
2021/01/15 Python
python实现学生通讯录管理系统
2021/02/25 Python
详解python第三方库的安装、PyInstaller库、random库
2021/03/03 Python
Cotton On美国网站:澳洲时装连锁品牌
2016/10/25 全球购物
PHP高级工程师面试问题推荐
2013/01/18 面试题
网络技术专业推荐信
2014/02/20 职场文书
团日活动总结报告
2014/06/25 职场文书
企业党建工作汇报材料
2014/08/19 职场文书
小学一年级班主任工作经验交流材料
2015/11/02 职场文书
公司员工违法违章行为检讨书
2019/06/24 职场文书
CSS元素定位之通过元素的标签或者元素的id、class属性定位详解
2022/09/23 HTML / CSS