javascript中闭包的概念是一个比较重要的概念。其实理解的也不是很透彻,只能先简单的做下笔记,更深入的了解,有待后面再继续学习。
在将闭包前,先说明几个与其相关的概念。
嵌套函数
在javascript中,函数是可以被嵌套在其他函数里面的,如:
function hypotenuse(x,y){
function square(a){
return a*a;
}
return Math.sqrt(square(x)+square(y));
}
alert(hypotenuse(2,0)); //2
嵌套函数一个很重要的特征是:
它们可以访问嵌套它们的函数的变量和参数。拿上面那个例子来说,内部函数square()可以访问嵌套它的函数hypotenuse()的参数x和y。
作用域
所谓作用域就是指变量或函数可访问的范围。作用域可以分为两种,一种是全局作用域,另一种是局部作用域。
全局作用域
在代码的任何地方都能被访问到的对象拥有全局作用域,换句话说,全局变量在代码的任何地方都能被访问。一般来说以下几种情况将拥有全局作用域:
1)在代码顶层定义的函数和变量将拥有全局作用域,如:
//在代码顶层定义name
var name="Neverland-7";
//在代码顶层定义sayName
function sayName(){
var innerName="linn721";
return innerName;
}
//访问全局变量name
alert(name); //Neverland-7
//访问全局函数sayName
alert(sayName()); //linn721
//访问局部变量innerName
alert(innerName); //error!
2)所有未定义便直接赋值的变量会自动声明为全局变量,拥有全局作用域,如:
function sayName(){
name="Neverland-7";
}
alert(name); //Neverland-7
在函数sayName内部,name变量没有声明,便直接被赋值。但是在函数外部,却可以访问得到它,原因就是它默认被设置为全局变量,拥有了全局作用域。
3)所有window对象的属性有用全局作用域
一般情况下,window对象的内置属性都拥有全局作用域,例如window.name、window.location、window.top等等。
局部作用域
局部作用域是指在一段代码或函数内才可以访问的到。通常在函数内部声明定义的变量以及函数拥有的是局部作用域,在该函数外部不能访问它们,否则将产错误。
function sayName(){
//定义局部变量
var innerName="linn721";
return innerName;
}
alert(innerName); //error!
作用域链
每段javascript代码(全局代码或函数)都有一个与之关联的作用域链,它是一组对象的列表。这组对象是函数作用域中的对象的。作用域链决定了哪些数据能被函数访问。每个函数内部都有一个属性([[Scope]]),可用它表示作用域链。
function sum(x,y){
return x+y;
}
当上面的函数被创建时,它的作用域链中就会填入一个对象,该对象就是全局对象。如下图所示:
var total=sum(2,3);
当执行sum函数时,它会增加自己的作用域,并创建一个新的对象(称为“活动对象”),用来保存它的局部变量、命名参数、参数集合以及this。接着这个对象会被添加到作用域链的顶端。新的作用域链如下图所示:
每单需要查找变量时,总是从作用域链的前端开始向后搜索同名变量,(即从当前活动对象开始,向其父层开始一层层搜索),直到搜索到对应的变量为止才停止。假如在对应的作用域链上都未能搜索到该变量,最终会抛出一个错误异常。
垃圾回收
我们知道,全局变量是一直都存在的,而局部变量仅在函数执行过程中存在。也就是说,在函数执行过程中,会为局部变量在内存上分配空间,以便存储它们的值,然后在函数执行时就可以随时使用它们。但是,当函数执行结束,它们变没有存在的必要了,此时javascript会自动释放它们的内存空间,以便将来使用。当第二次调用函数时,又会重新为局部变量进行分配,不会保留之前的值:
function f1(){
var n=0;
return ++n;
}
alert(f1()); //1
alert(f1()); //1
由上面的例子可以看出,不管调用多少次f1(),返回的值都是1。这是因为,在执行完f1后,就没有其他任何地方有对它的引用了,javascript就会判定它内部的变量是无用的了,这时,当f1执行完后,其内部变量就会被销毁,所以每次调用f1()时,它内部的n的值都是为0。第一次调用的结果不会保留到第二次。
闭包
说了这么多,我们就可以开始讲闭包了,所谓的闭包,就是指有权访问另一个函数内部变量的函数。我们先来看一个例子:
function f1(){
var n=0;
function f2(){
n++;
return n;
}
return f2;
}
//f1()返回的是f2
alert(f1()); //function f2(){ n++; return n; }
var f3=f1(); //这句话其实是将f1的执行结果,也就是f2赋给f3
alert(f3()); //1
alert(f3()); //2
看这个例子,执行两次f3,第一次返回1,第二次返回2,对于这样的结果是否感到很诧异?按【垃圾回收】中提到的概念,局部变量在函数执行结束后就会被销毁,每次调用都是重新赋予新的内存空间的,按理来说,两次结果都是返回1的。那为什么两次结果不一样呢?这就是闭包在起作用了。
实际上,上面的代码就创建了一个闭包。f2在f1内部被创建,它会将f1的整个活动对象保存在自己的作用域链中,因此它可以访问f1中定义的变量n。f3是一个全局变量,它的内存是一直存在的,当它引用了f2,就使得f2不能被垃圾回收机制识别,javascript会认为它是有用的,所以在f1执行后它就不会回收,与此同时,f2也有对f1的变量的引用(即n),所以n也不会被销毁。这样就导致了当两次执行f3,n的值会递增。这也体现了闭包的一个特性:它们可以捕捉到局部变量和参数,并一直保存下来。
但是有一点需要注意,假如将f1的执行结果赋给不同的变量, 那么这这两个变量时相互独立的,不会互相引用对方的n的值:
function f1(){
var n=0;
function f2(){
n++;
return n;
}
return f2;
}
var f3=f1();
var f4=f1();
alert(f3()); //1
alert(f4()); //1
alert(f3()); //2
alert(f4()); //2
alert(f4()); //3
当f3和f4分别调用了f1(),实际上是产生了两个闭包实例,它们内部引用的n分别属于各自的运行环境。可以理解为,它们都拥有了n的一个副本,所以两个是相互独立的。
闭包的用途
通过上面的例子,我们可以看出,闭包有以下两种用途:
- 可以通过闭包,使外部函数可以访问内部函数的变量;
- 闭包可以让局部变量始终保存在内存中,而不被销毁。
使用闭包的注意点
使用闭包要注意的问题,恰好是由它的用途所引发的:
1)闭包会使变量一直保存在内存中,很容易造成循环引用,所以不能滥用闭包。在不得不使用闭包时,要切记在退出函数时,将不再使用的局部变量删除,以解除对它们的引用,好让垃圾回收机制能将它们的内存清除。
2)在闭包中使用this要特别小心。来看一个例子:
var name="The window";
var myobject={
name:"My Object",
getName:function(){
return function(){
return this.name;
}
}
}
var resultName=myobject.getName();
alert(resultName()); //The window
可能在没看结果的时候,你会以为是返回"My Object",因为造我们之前的理解,getName函数中的this应该是指myobject这个对象的。但是,闭包改变了它。
getName这个函数里面返回了一个匿名函数,这匿名函数返回它运行环境中存在的name的值。当执行完var resultName=myobject.getName()这句时,实际上是将它里面的匿名函数的引用给了resultName。当执行resultName()时,它是在全局作用域中执行的,因此this对象就指向了全局环境,全局环境中的name属性的值是"The window"。所以最终结果是"The window"。
那么能否得到我们期望的结果,返回"My Object"呢?答案是可以的:
var name="The window";
var myobject={
name:"My Object",
getName:function(){
var that=this;
return function(){
return that.name;
}
}
}
var resultName=myobject.getName();
alert(resultName()); //My Object
这边所做的改变仅仅是在getName函数内部,将this保存在一个变量that中,此时,that保存的是myobject的执行环境。当在执行resultName()时,它返回的是that.name;that在getName中有定义,它是的执行环境是myobject,而在myobject中也有定义name,它的值是"My Object"。所以,最终结果是"My Object"。
到此,对闭包的理解就结束了。可能有些地方是不正确的。希望大家指正哈~~