数据存储在哪里,关系到代码运行期间数据被检索到的速度。在JavaScript中,此问题相对简单,由于数据存储只有少许方式可供选择。正如其余语言那样,数据存储位置关系到访问速度。在JavaScript中有四种基本的数据访问位置:前端
1.Literal values 直接量web
直接量仅仅表明本身,而不存储于特定位置。 JavaScript的直接量包括:字符串,数字,布尔值,对象,数组,函数,正则表达式,具备特殊意义的空值,以及未定义。正则表达式
2.Variables 变量编程
开发人员使用var关键字建立用于存储数据值。数组
3.Array items 数组项浏览器
具备数字索引,存储一个JavaScript数组对象。函数
4.Object members 对象成员性能
具备字符串索引,存储一个JavaScript对象。优化
每一种数据存储位置都具备特定的读写操做负担。大多数状况下,对一个直接量和一个局部变量数据访问的性能差别是微不足道的。访问数组项和对象成员的代价要高一些,具体高多少,很大程度上依赖于浏览器。总的来讲,直接量和局部变量的访问速度要快于数组项和对象成员的访问速度。,若是关心运行速度,那么尽可能使用直接量和局部变量,限制数组项和对象成员的使用。this
1.做用域链和标识符解析
每个JavaScript函数都被表示为对象。进一步说,它是一个函数实例。函数对象正如其余对象那样,拥有你能够编程访问的属性,和一系列不能被程序访问,仅供JavaScript引擎使用的内部属性。其中一个内部属性是[[Scope]],由ECMA-262标准第三版定义。内部[[Scope]]属性包含一个函数被建立的做用域中对象的集合。此集合被称为函数的做用域链,它决定哪些数据可由函数访问。此函数做用域链中的每一个对象被称为一个可变对象,每一个可变对象都以“键值对”的形式存在。当一个函数建立后,它的做用域链被填充以对象,这些对象表明建立此函数的环境中可访问的数据。
当代码在一个环境中执行时,会建立变量对象的一个做用域链(scope chain,不简称sc)来保证对执行环境有权访问的变量和函数的有序访问。做用域第一个对象始终是当前执行代码所在环境的变量对象(VO)
例如:
function add(x,y){
var b=x+y;
return b;
}
当add()函数建立后,它的做用域链中填入一个单独的可变对象,此全局对象表明了全部全局范围定义的变量。此全局对象包含诸如窗口、浏览器和文档之类的访问接口。
如图:
上图就是函数Add()的做用域链。
Add函数的做用域链将在运行时用到。若是运行下面的代码
var total = add(5, 10);
运行此add函数时创建一个内部对象,称做“运行期上下文”。一个运行期上下文定义了一个函数运行时的环境。对函数的每次运行而言,每一个运行期上下文都是独一的。因此每次调用同一个函数就会致使多处调用上下文。当函数执行完毕,运行期上下文就被销毁。
一个运行期上下文有它本身的做用域链,用于标示符解析。当运行期上下文被建立时,它的做用域被初始化,连同运行函数的[[Scope]]属性中所包含的对象。这些值按照它们出如今函数中的顺序,被复制到运行期上下文的做用域链中。这项工做一旦完成,一个被称做“激活对象”的新对象就为运行期上下文建立好了。此激活对象做为函数执行期的一个可变对象,包含访问全部局部变量,命名参数,参数集合,和this的接口,而后,这个对象被推入做用域的前端。看成用域链被销毁时,激活对象也一同销毁。
上图是运行时Add()函数的做用域链。
在函数运行过程当中,没遇到一个变量,标识符识别过程要决定从哪里得到或者存储数据,此过程收索运行期上下文的做用域链,查找同名的标识符。搜索工做从运行函数的激活目标之做用域链的前端开始。若是找到了,那么就使用这个具备指定标识符的变量,若是没有找到,搜索工做将进入做用域链的下一个对象。此过程持续进行,直到找到标示符。若是整个过程都没有找到那么被认为是undefined。正是这种搜索过程影响了性能。
2.标识符解析的性能
标示符识别不是免费的,事实上没有哪一种电脑操做能够不产生性能开销。在运行期山下文的做用域链中,一个标示符所处的位置越深,它的读写速度就越慢。因此,函数中局部变量的访问速度老是最快的,而全局变量一般是最慢的(优化的JavaScript引擎在某些状况下能够改变这种状况,如谷歌浏览器)。全局变量老是处于运行前上下文做用域链的最后一个位置。因此老是最远才能触及。
用局部变量存储本地范围以外的变量值,若是它们在函数中的使用多于一次。考虑下面的例子:
function initUI(){ var
bd = document.body, links = document.getElementsByTagName_r("a"),
i = 0, len = links.length;
while(i < len){ update(links[i++]); } document.getElementById("go-btn").onclick = function(){ start(); }; bd.className = "active"; }
此函数包括三个对document的引用,document是一个全局对象。搜索此变量,必须遍历整个做用域链,指导最后在全局变量对象中找到它。你能够经过这种方法减轻重复的全局变量访问对性能的影响;首先将全局变量的引用放在一个局部变量中,而后使用整个局部变量代替全局变量。 代码重写以下:
function initUI(){ var doc = document, bd = doc.body, links = doc.getElementsByTagName_r("a"), i = 0, len = links.length;
while(i < len){ update(links[i++]);
} doc.getElementById("go-btn").onclick = function(){
start();
}; bd.className = "active";
}
initUI()的新版本首先将document的引用存入局部变量doc中,如今访问全局变量的次数是1次,而不是3次。用doc替代document更快,由于它是一个局部变量。固然,这个简单的函数不回显示出巨大的性能改进,由于数量的缘由。不过若是几十个全局变量被反复访问,那么性能改进将明显的多么出色。
3.改变做用域链
通常来讲,一个运行期上下文的做用域链不会忽然被改变。可是,有两种表达式能够在运行时临时改变运行期上下文做用域链。第一个是with表达式。
with表达式为全部的对象属性建立了一个默认操做变量。在其余语言中,相似的功能一般用来避免书写一些重复的代码。initUI()函数能够重写成以下样式:
function initUI(){ with (document){ //avoid! var bd = body, links = getElementsByTagName_r("a"), i = 0, len = links.length; while(i < len){ update(links[i++]); } getElementById("go-btn").onclick = function(){ start(); }; bd.className = "active"; } }
此重写的initUI()版本使用了一个with表达式,避免屡次书写document,这看起来彷佛更有效率,而实际上却产生了一个性能问题。
当代码流执行到一个with表达式时,运行期上下文的做用域链被临时改变了。一个新的可变对象将被建立,她包含指定对象的全部属性。此对象被插入到做用域链的前端,意味着如今函数的全部局部变量都被推入第二个做用域链对象中,因此访问代价更高了。
经过将document对象传递给with表达式,一个新的可变对象容纳了document对象的全部属性,被插入到做用域链的前端。这使得访问document的属性很是快,可是访问局部变量的速度却变慢了,例如bd变量。正由于这个缘由,最好不要使用with表达式。正如前面提到的,只要简单地将document存储在一个局部变量中,就能够得到性能上的提高。
在JavaScript中不仅是with表达式人为地改变运行期上下文的做用域链,try-catch表达式的catch子句具备相同效果。当try块发生错误时,程序流程自动转入catch块,并将异常对象推入做用域链前端的一个可变对象中。在catch块中,函数的全部局部变量如今被放在第二个做用域链对象中。例如:
try {
methodThatMightCauseAnError();
} catch (ex){
alert(ex.message); //scope chain is augmented here
}
可是,只要catch子句执行完毕,做用域链就会返回到原来的状态。
若是使用得当,try-catch表达式是很是有用的语句,因此不建议彻底避免。若是你计划使用一个try-catch语句,请确保你了解可能发生的错误。一个try-catch语句不该该做为JavaScript错误的解决办法。若是你知道一个错误会常常发生,那说明应当修正代码自己的问题。
你能够经过精缩代码的办法最小化catch子句对性能的影响。一个很好的模式是将错误交给一个专用函数来处理。如:
try {
methodThatMightCauseAnError();
} catch (ex){
handleError(ex); //delegate to handler method
}
handleError()函数是catch子句中运行的惟一代码。此函数以适当方法自由地处理错误,并接收由错误产生的异常对象。因为只有一条语句,没有局部变量访问,做用域链临时改变就不会影响代码的性能。