高性能的JavaScript--数据访问(1)

写在前面

数据存储在哪里,关系到代码运行期间数据被检索到的速度。在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子句中运行的惟一代码。此函数以适当方法自由地处理错误,并接收由错误产生的异常对象。因为只有一条语句,没有局部变量访问,做用域链临时改变就不会影响代码的性能。

相关文章
相关标签/搜索