JS中的闭包(closure)

先从一个例子入手:

function fun(){
    var count = 0;
    return function(){
        return ++count;
    }
}
var func = fun();
alert(func()); // 1
alert(func()); // 2

简单理解:闭包是一种具有状态的函数,或者将闭包的特征理解为,其相关的局部变量在函数调用结束后将会继续存在。从上面的例子中可以看出来,函数内的局部变量count在函数fun()调用之后依然有效。

步步深入闭包:

  • 嵌套的函数声明
    闭包的前提条件是需要在函数声明的内部声明另一个函数(即嵌套的函数声明)就像下面这样:
function foo(){
    function bar(){
        alert("bar is called !");
    }
    bar();
}
foo(); // bar is clalled !

在函数foo()中包含函数bar()的声明以及调用语句,在调用foo()函数时,就间接地调用了函数bar()。我们可以这么理解,变量foo是全局对象的属性,而bar是foo对象的一个属性。也没有太多不好理解的地方。

  • 嵌套函数与作用域
    对上面的代码作少许修改,像下面这样:
function foo(){
    var number = 123;
    function bar(){
        alert(number);
        alert("bar is called !");
    }
    bar();
}
foo(); 
// 123
// bar is clalled !

代码很直观,这里主要说说作用域,即在内层进行声明的函数bar(),可以访问外层的函数foo()的局部变量number,再往深的探讨:
之所以可以访问外部的变量,是因为内部函数的作用域链中包含了外部函数的作用域,这一句先放在这里,回过头来再看这一句
几个概念:

  • 执行环境(execution context)
  • 活动对象(activation object)
  • 变量对象(variable object)

当某个函数被调用时,会创建一个执行环境及相应的作用域链,然后使用arguments和其他命名参数的值来初始化函数的活动对象,但在作用域链中, 外部函数的活动对象始终处于第二位,外部函数的外部函数活动对象处于第三位….直至终点的全局执行环境。
每个执行环境都有一个表示变量的对象即变量对象,当调用foo()时,会创建一个包含arguments、number的活动对象(局部活动对象),全局执行环境的变量对象(foo 【调用的地方,敞开在全局环境中,区分foo()函数是局部环境的变量】)在foo()执行环境的作用链中则处于第二位,全局环境的变量对象始终存在,这里foo()函数是局部环境的变量,只在函数执行的过程中存在
无论什么时候在函数中访问一个变量时,就会从作用域链中搜索具有相应名字的变量,一般来说,当函数执行完毕后,局部活动对象就会被销毁了(foo()函数以及arguments、number),但是对于我们上面这个例子代码中的情况并非如此,因为这里有闭包。虐你千面遍!
再来看看上面刚被搁置一旁的那句话:之所以可以访问外部的变量,是因为内部函数的作用域链中包含了外部函数的作用域
bar()函数从foo()中被返回后,它的作用域链被初始化为包含foo()函数的活动对象和全局变量对象,这样,bar()就可以访问在foo()中定义的所有变量,还有就是,当foo()函数被执行完毕后,其活动对象也不会被销毁,因为bar()的作用域链还在引用这个活动对象,换句话说,当foo()函数返回后,其执行环境的作用域链会被销毁,但它的活动对象仍然会留在内存中,直至bar()函数被销毁后,foo()的活动对象才会被销毁!晕没,我是有点晕了。

  • 嵌套函数的返回
    对上面的代码作一点修改,像下面这样,把bar()返回
function foo(){
    var number = 123;
    function bar(){
        alert(number);
        alert("bar is called !");
    }
    return bar;
}
foo();
//结果是:function bar(){
//          alert(number);
//          alert("bar is called !");
//      }

返回的是一个Function对象,对比一下这里….,现在接下来把它赋给一个变量,像下面这样:

var barGet = foo();
barGet();
//123
//bar is called !

结果说明 ,可以从函数foo()的外部调用bar(),进一步说,函数foo()的局部变量在函数foo()被调用后依然存在!这和java中语法是相反的,java中的局部变量离开函数后,局部变量就会失效!
接下来看一个闭包比较有意思的地方:闭包与执行环境

function foo(arg){
    var number = arg + 10;
    function bar (){
        alert("number is " + number);       
   }
   return bar;
}
var bar1 = foo(10);
var bar2 = foo(20);
bar1(); // number is 20
bar2(); // number is 30
var number = 50; 
bar2(); // number is 30

虽然后面把number设成了全局变量,并赋给了新值 ,但是调用 bar2()时,number 的值仍然是30, 所以可以这么理解闭包:闭包指的是一种特殊的函数,这种函数会在被调用时保持当时的变量名查找的执行环境。
再通过下面的这段代码理解一下上面的那句话:

fucntion foo(arg){
    var number = 123;
    function bar(){
        alert(number);
    }
    number++;
    function bar1(){
        alert(number);
    }
    return [bar,bar1];
}
var bar = foo(100);
bar[0](); // 124
bar[1](); // 124

两个结果都是124,再回过头看看前面加粗的那一句话,number作为foo()函数的局部变量,也就是说因为bar()是一个闭包的原因,在foo()函数内部的执行环境下,number的值是固定的,不管number++放在bar()前还是后面。亦或是放在bar()函数内部,结果都是一样的。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值