Javascript中有四种基本的数据访问方式:字面量
、变量
、数组元素
、对象成员
,几乎所有的浏览器中:字面量和本地变量的访问速度要比数组元素和对象成员的访问速度快的多
12bet,中的函数都是对象,更具体来说,函数都是Function的实例。每个函数都有自己的属性就和其他对象一样,函数的一个重要属性就是[[scope]]。[[scope]]就是函数的作用域链,它是在函数创建时就生成的。例如如下代码:
function add(num1, num2){
var sum = num1 + num2;
return sum;
}
上面的add
函数的作用域链值包含全局对象。
12bet,当函数执行时,就会创建一个“执行上下文(excution context)”的内部对象,每个运行期上下文都有自己的作用域链,用于标识符解析,当运行期上下文被创建时,12博体育,而它的作用域链初始化为当前运行函数的[[Scope]]所包含的对象。
12bet,这些值按照它们出现在函数中的顺序被复制到运行期上下文的作用域链中。它们共同组成了一个新的对象,叫“活动对象(activation object)”,该对象包含了函数的所有形式参数、arguments集合、局部变量以及this,然后此对象会被推入作用域链的前端,当运行期上下文被销毁,12博体育,活动对象也随之销毁。新的作用域链如下图所示:
函数每次执行时对应的运行期上下文都是独一无二的,所以多次调用同一个函数就会导致创建多个运行期上下文,当函数执行完毕,执行上下文会被销毁。每一个运行期上下文都和一个作用域链关联。一般情况下,在运行期上下文运行的过程中,其作用域链只会被with
语句和catch
语句影响。
with
语句是对象的快捷应用方式,用来避免书写重复代码。例如:
function initUI(){
with(document){
var bd=body,
links=getElementsByTagName("a"),
i=0,
len=links.length;
while(i < len){
update(links[i++]);
}
getElementById("btnInit").onclick=function(){
doSomething();
};
}
}
当代码运行到with
语句时,运行期上下文的作用域链临时被改变了。一个新的可变对象被创建,它包含了参数指定的对象的所有属性。这个对象将被推入作用域链的头部,这意味着函数的所有局部变量现在处于第二个作用域链对象中,因此访问代价更高了。如下图所示:
catch
也会改变作用域链,要注意的是不要将catch
作为解决错误的方式。为了减少catch
对于性能的影响,应该尽量减少catch
中的代码量,一般的做法是使用一个错误处理方法。
try {
doSomething();
} catch(ex) {
handleError(ex); //委托给处理器方法
}
with
、catcch
、eval
都有动态作用域,动态作用域只是在代码执行时存在,但是无法通过静态分析他们。
闭包用一句话形容就是:函数里再定义函数,内部函数可以访问外部函数的变量,这样就形成了闭包。闭包的形成和作用域有密切的关系,例如下面的例子:
function assignEvents(){
var id = "xdi9592";
document.getElementById("save-btn").onclick = function(event){
saveDocument(id);
};
}
assignEvents
函数中为一个DOM元素绑定了事件处理函数,这个内部函数使用了外部的assignEvents
函数中的变量id
,这时候就创建了一个闭包。
当assignEvents
函数创建的时候其作用域([[scope]])中只有全局对象,执行assignEvents
函数的时候会创建一个活动对象放于作用域链的顶部,正常情况下assignEvents
函数运行结束后活动对象就会被垃圾回收。但是当运行到内部函数的定义处时,就会创建内部函数的作用域([[scope]]),这个内部函数的作用域包含父级的活动对象和全局对象。这样就形成了闭包,当assignEvents
函数执行结束时,由于有对象引用assignEvents
函数的活动对象,那么这个活动对象就不会被垃圾回收,这样就形成了闭包。如下图所示:
闭包的缺点:
对象都用两种成员:实例成员、原型成员,实例成员就是自己的成员,原型成员是继承自对应原型的成员。例如:
var book = {
title: "High Performance JavaScript",
publisher: "Yahoo! Press"
};
创建一个函数时,会自动生成对应的prototype
对象(显式原型),创建实例时就会将实例的__proto__
属性(隐式原型)指向显式原型对象,因此显式原型是共享的。如下例所示:
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");
常常会看到window.location.href
这种Javascript代码,这样的嵌套对象会使Javascript引擎每次都扫描所有的成员来解析成员,所以嵌套成员越深,访问速度越慢。执行location.href
总是比window.location.href
更快。
对于那些不止访问一次的成员可以使用本地变量存储起来,例子如下:
function toggle(element){
if (YAHOO.util.Dom.hasClass(element, "selected")){
YAHOO.util.Dom.removeClass(element, "selected");
return false;
} else {
YAHOO.util.Dom.addClass(element, "selected");
return true;
}
}
可以使用下面的方式改造来提高性能:
function toggle(element){
var Dom = YAHOO.util.Dom;
if (Dom.hasClass(element, "selected")){
Dom.removeClass(element, "selected");
return false;
} else {
Dom.addClass(element, "selected");
return true;
}
}