javascript中的闭包

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. 闭包可以让局部变量始终保存在内存中,而不被销毁。

使用闭包的注意点

使用闭包要注意的问题,恰好是由它的用途所引发的:

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"。


到此,对闭包的理解就结束了。可能有些地方是不正确的。希望大家指正哈~~



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值