提到闭包总给人很高深的感觉,网上的例子也数不胜数。但是我发现相当一部分并不容易理解。根据我的观察,是因为这些例子把标题中提到的概念糅杂在了一起,往往越看越糊涂。所以我希望化整为零,拆成简单例子来解释。
1.先看作用域:
JavaScript作用域只有两种——全局作用域和函数内作用域,没有代码块作用域。示例:
function loop(){
for(var i=0;i<5;i++){
//doSomething;
}
alert(i);
}
loop(); //执行函数结果为5。
尽管变量i已经脱离了循环代码块,但因为JavaScript没有代码块级作用域,所以i的作用域是在整个函数内。循环结束后仍然保持为5。当然作为局部变量,函数执行完毕就释放了。
全局作用域就好理解了。变量在整个页面的执行环境下都有效。定义的方法有两种:一是在所有函数外定义的变量即为全局变量;二是定义变量时不加var,此时被认为是全局变量。
例如:
<script>
var i=10; //全局变量
function f1(){
times=3; //全局变量
}
</script>
2.上下文环境与"this"
上下文环境说的是对象(函数)的“继承关系”,而非函数之间的调用关系。函数间的互调用并不改变上下文环境,体现函数间互调用有另外一个变量caller表示。示例:
function a(){
b();
}
function b(){
alert(this);//被别的函数调用不改变上下文环境,仍然指向window。
}
a();
体现函数互调用关系的是caller:
function a(){
b();
}
function b(){
alert(b.caller);
}
a();
通过this可以很好的观察某个对象(函数)的上下文环境,在传统语言里this不难理解,指的是对象本身。在JavaScript中,函数也是个对象。如果此函数是个独立执行的函数,那么this就意味着最外层的上下文环境,也即window;如果函数定义为某个对象的属性,那么它的上下文环境就指向了这个对象。
示例:
function a(){
alert(this.name);
}
var obj1={
name:"I am obj1",
method:a //属性是一个函数
}
obj1.method(); //函数对象的上下文是obj1
此外使用关键字call、apply可以显式改变函数的上下文环境
例如:
function a(){
alert(this.name);
}
var obj1={
name:"I am obj1",
}
a.call(obj1); //函数的上下文是obj1
3.函数对象的定义和调用
这个本来不值一提,定义是定义,调用是调用————调用是加"()"。但是我经常看走眼,年纪大了老眼昏花。但也确实有不太好分辨的时候。比如这样:
var foo=function outer(){
return function inner(){
alert(this);
};
}();
foo();
这个例子中,foo是outer的执行结果。outer的执行结果返回的还是一个函数,所以foo也还是一个函数(其实就是inner啦,但此时并没有执行inner)。那么再执行foo的时候,上下文其实是window,自然也返回window。
4.匿名函数
匿名函数因为没有被哪个变量“记住”,所以只能定义完立即执行,通常执行的上下文是window。
比如这样:
(function(){
alert(this);
})()
更复杂一点的:
(function outter(){
return function inner(){
alert(this);
};
})()(); //此处和3小节的代码效果是一模一样的
5.闭包指的是有权访问另一个函数作用域中的变量的函数。很多开发人员混淆闭包和匿名函数——书上说的。这是因为闭包时常是以匿名函数形式来实现的,但其实二者无关。匿名函数可以单独执行,如4小节示例;闭包也可以由非匿名函数实现,例如:
function fout(){
var n=123;
return function fin(){
alert(n); //123
}
}
var f=fout();
f(); //fin访问了fout中的变量,所以尽管fout已执行完毕,但局部变量n仍被保留,从而形成闭包。
当然改成匿名函数也并非难事:
function fout(){
var n=123;
return function(){
alert(n); //123
}
}
var f=fout();
f();
总之,JavaScript是一门很有特色的语言。参考我的另一篇《JavaScript特点》