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

动态做用域

不管是with表达式仍是try-catch表达式的catch子句,以及包含()的函数,都被认为是动态做用域。一个动态做用域只由于代码运行而存在。所以没法经过静态分析(查看代码机构)来肯定(是否存在做用域)。例如:编程

function execute(code) {
(code);
function subroutine(){
return window;
}
var w = subroutine();
//what value is w?
};

execute()函数看上去像一个动态做用域,由于它使用了()。w变量的值与code有关。大多数状况下,w将等价于全局变量window对象,可是请考虑以下状况:数组

execute("var window = {};")

这种状况下,()在execute()函数中建立了一个局部的window变量,因此这个w将等价于这个局部的window变量而不是全局的那个。因此说,不运行这段代码是没有办法了解具体状况的,标识符window的确切含义不能预先肯定。浏览器

 

闭包,做用域,和内存

 闭包是JavaScript最强大的一个方面,它容许函数访问局部范围以外的数据。闭包的使用在当今最复杂的网页应用中无处不在,不过,有一种性能影响与闭包有关。缓存

为了解闭包的有关性能问题,考虑下面的例子:闭包

function assignEvents(){
var id = "xdi9592";
document.getElementById("save-btn").onclick = function(event){
saveDocument(id);
};
}

assignEvents()函数为一个dom元素制定了一个事件处理句柄,此事件处理句柄是一个闭包,当assignEvents()执行时建立,能够访问其范围内部的id变量,用这种方法封闭对id变量的访问,必须建立一个特定的做用域链。dom

当assignEvents()被执行时,一个激活对象被建立,并包含了一些应有的内容,其中包括id变量。它将成为运行期上下文做用域链上的第一个对象,全局对象是第二个。当闭包建立时,[[Scope]]属性与这些对象一块儿被初始化。函数

 

因为闭包的[[Scope]]属性包含与运行期上下文做用域链相同的对象引用,会产生反作用。一般,一个函数的激活对象与运行期上下文一同销毁。当涉及闭包时,激活对象就没法销毁了,由于引用任然存在于闭包的[[Scope]]属性中,这意味着脚本中的闭包与非闭包函数相比,须要更多的内存开销。在大型网页应用中,这多是个问题,尤为在IE中更被关注。IE使用非本地JavaScript对象实现DOM对象,闭包可能致使内存泄露。性能

当闭包被执行时,一个运行期上下文将被建立,它的做用域链与[[Scope]]中引用的两个相同的做用域同时被初始化,而后一个新的激活对象为闭包自身被建立。this

主要闭包中使用的两个标识符,id和saveDocument,存在于做用域链第一个对象以后的位置上。这是闭包最主要的性能关注点:你常常访问一些范围以外的标识符每次访问都致使一些性能损失。spa

在脚本中最好是当心地使用闭包,内存和运行速度都值得被关注。将经常使用的域外变量存入局部变量中,而后直接访问局部变量。

对象成员

 大多数JavaScript代码以面向对象的形式编写。不管经过建立自定义对象仍是使用内置对象,诸如文档对象模型(DOM)和浏览器对象模型(BOM)之中的对象。所以,存在不少对象成员访问。

对象成员包括属性和方法,在JavaScript中,两者差异甚微。对象的一个命名成员能够包括任何数据类型。既然函数也是一种对象,那么对象成员除了传统的数据类型外,也能够包含一个函数。当一个成员用了一个函数时,它被称做一个“方法”,而一个非函数类型的数据则被称做“属性”。

原形

 对象成员比直接量或局部变量访问速度慢,在某些浏览器上比访问数组项还要慢。这和JavaScript中对象的性质有关。

JavaScript中的对象是基于原形的,原形是其余对象的基础,定义并实现了一个新对象所必须具备的成员。这一律念彻底不一样于传统面向对象编程中“类”的概念,它定义了建立新对象的进程。原形对象为给定类型的对象实例所共享,所以全部实例共享原型对象的成员。

一个对象经过一个内部属性绑定到它的原形。Firefox ,Safari和Chrome向开发人员开放这一属性,称做__proto__,其余浏览器貌似不容许脚本访问这一属性。任什么时候候你建立一个内置类型的实例,如object或者Arrary,这些实例自动拥有一个Object做为他们的原形。

所以,对象能够有两种类型的成员:实例成员(“own”成员)和原造成员。实例成员直接存在于实例自身,而原造成员则从对象原形继承。如:

var book = {
title: "High Performance JavaScript",
publisher: "Yahoo! Press"
};
alert(book.toString()); //"[object Object]"

如 book对象有两个实例成员:title 和publisher,注意它并无定义tostring()接口,可是这个接口却被调用了,也没用抛出错误。toString()函数就是一个book对象继承的原造成员。

原形链

 

对象的原形决定了一个实例的类型。默认状况下,全部对象都是Object 的实例,并继承了全部基本方法。如toString()。也能够用“构造器”建立另一种类型的原形。

 

function Book(title, publisher){
this.title = title;
this.publisher = publisher;
}
Book.prototype.sayTitle = function(){
alert(this.title);
};
var book1 = new Book("High Performance JavaScript", "Yahoo! Press");
var book2 = new Book("JavaScript: The Good Parts", "Yahoo! Press");
alert(book1 instanceof Book);  //true
alert(book1 instanceof Object);  //true
book1.sayTitle();  //"High Performance JavaScript"
alert(book1. toString()); //"[object Object]"

Book构造器用于建立一个新的Book实例。book1的原形(__proto__)是Book.prototype,Book.prototype的原形是Object。这就建立了一个原形链,book1和book2继承了他们的成员。
主要的是,两个Book实例共享同一个原形链。每一个实例拥有本身的title和publisher属性,可是其余成员均继承自原形。当book1.toString()被调用时,搜索工做必须深刻原形链才能找到对象成员"toString",深刻原形链越深,搜索速度就越慢。每深刻原形链一层都会增长性能损失。搜索实例成员的过程比访问直接量或者局部量负担更重,因此增长遍历原形链的开销正好放大了这种效果。

嵌套成员

因为对象成员可能包含其余成员,例如不太常见的写法window.location.href这种模式。每遇到一个点号,JavaScript引擎就要在对象成员上执行一次解析过程。成员嵌套越深,访问速度越慢。location.href老是快于window.location.href,然后者也要比window.location.href.toString()更快。若是这些属性不是对象的实例属性,那么成员解析还要在每一个点上索搜原形链,这将须要更长时间。

缓存对象成员的值

因为全部这些性能问题与对象成员有关,因此若是可能的话就避免使用他们。更确切的说,只有在必要状况下使用对象成员。例如没有理由在一个函数中屡次读取同一个对象成员的值:

function hasEitherClass(element, className1, className2){
return element.className == className1 || element.className == className2;
}

element.className被访问了两次,咱们能够存入一个局部变量,消除一次搜索过程:

function hasEitherClass(element, className1, className2){
var currentClassName = element.className;
return currentClassName == className1 ||  currentClassName == className2;
}

通常来讲,若是在同一函数中你要屡次读取同一个对象属性,最好将它存入到一个局部变量。以局部变量替代属性,避免多余的属性查找带来的性能开销。在处理嵌套对象成员时这点特别重要,他们会对运行速度产生难以置信的影响。

总结

1.在JavaScript中,数据存存储的位置能够对代码总体性能产生重要影响。有4种数据类访问类型:直接变量,变量,数组项,对象成员。他们有不一样的性能考虑。

2.直接变量和局部变量访问速度很是快,数组项和对象成员须要更长时间。

3.局部变量比域变量快,由于它位于做用域链的第一个对象中。变量在做用域链中的位置越深访问所需的时间就越长。全局变量老是最慢的,由于它们老是位于做用域链的最后一环。

4.避免使用with表达式,由于它改变了运行期上下文的做用域链。并且应当当心对待try-catch表达式catch子句,由于它具备一样的效应。

5.嵌套对象成员会形成重大性能影响,尽可能少用。

6.一个属性或方法在原形链中的位置越深,访问速度就越慢。

相关文章
相关标签/搜索