js闭包

来源:互联网 发布:数据分析是什么 编辑:程序博客网 时间:2024/05/21 11:28

前言

在js中,闭包是一个很重要又相当不容易完全理解的要点,网上关于讲解闭包的文章非常多,但是并不是非常容易读懂,在这里以《javascript高级程序设计》里面的理论为基础。用拆分的方式,深入讲解一下对于闭包的理解,如果有不对请指正。

写在闭包之前

闭包的内部细节,依赖于函数被调用过程所发生的一系列事件为基础,所以有必要先弄清楚以下几个概念:

1.执行环境和活动对象

执行环境(execution context)定义了变量或者函数有权访问的其他数据,每个执行环境都有一个与之关联的变量对象(variable object),执行环境中定义的变量和函数就保存在这个变量对象中;全局执行环境是最外围的一个执行环境,通常被认为是window对象执行环境和变量对象在运行函数时生成
执行环境中的所有代码执行完以后,执行环境被销毁,保存在其中的变量和函数也随之销毁;(全局执行环境到应用退出时销毁)

2.作用域链

当代码在一个执行环境中执行时,会创建变量对象的一个作用域链(scope chain),作用域链用来指定执行环境有权访问的所有变量和函数的访问顺序;作用域链的最前端,始终是当前代码执行环境的变量对象,如果这个环境是函数,则其活动对象就是变量对象,作用域链的下一个变量对象,来自外部包含环境,再下一个变量对象,来自下一个外部包含环境,以此类推直到全局执行环境,在函数执行过程,根据当前执行环境的作用域链来逐层向外查找变量,并且进行标识符解析

样例1

var a=1;function A(a){    return a+2;}A(1);

这里写图片描述
如图所示,在执行函数A的时候,创建了A的执行环境和变量对象,其中A的变量对象和全局变量对象中都含有a变量,根据作用域链从前向后查找,在A的变量对象中找到,所以输出1,执行完毕以后 ,A的指向环境销毁,A的变量对象由于没有被引用,所以也销毁

样例2

function A(a){    var a=1;    function B(){        alert(a+1);    }}A(1);

上面这个例子,在函数A中定义了函数B,关系图如下:
这里写图片描述
从图上可以很清楚的看出,在每个执行环境中可以访问到的变量对象,所以B可以访问A的变量对象和全局变量对象中的变量以及自身变量对象,A可以访问自身变量对象和全局变量对象

3.初涉闭包

闭包是指有权访问另一个函数作用域变量的函数,创建闭包的通常方式,是在一个函数内部创建另一个函数.
上文我们提到了,由于作用域链的结构,外围函数是无法访问内部变量的,为了能够访问内部变量,我们就可以使用闭包,闭包的本质还是函数

4.闭包详解

难点一:判断作用域指向的变量对象是否相同

function A(){    var x = 1;    return function(){        x++;        console.log(x);    }}var m1 = A();//第一次执行A函数m1();//2m1();//3var m2 = A();//第二次执行A函数m2();//2m1();//4

上面这个例子其实可以引出几个问题:

  1. 为什么连续执行m1的时候,x的值在递增?
  2. 定义函数m2的时候,为什么x的值重新从1开始了?
  3. 运行m2以后,为什么再运行m1,x还是按照之前m1的运行结果继续增长?(其实就是m1和m2里面的x为什么是相互独立,各自维持的?)

其实要解决上面的问题,我们就要用到前面铺垫的知识点了:
这里写图片描述
每次执行A函数时,都会生成一个A的活动变量和执行环境,执行完毕以后,A的执行环境销毁,但是活动对象由于被闭包函数引用,所以仍然保留,所以,最终剩下两个A的变量对象,因此m1和m2在操作x时,指向的是不同的数据,

现在来回答上面的三个问题:

  1. answer:因为m1在引用的活动对象A一直没有释放(想释放的话可以让m1=null),所以x的值一直递增。
  2. answer:因为又一次运行了A函数,生成一个新的A的活动对象,所以m2的作用域链引用的是一个新的x值。
  3. answer:因为在定义m1和m2的时候,分别运行了A函数,生成了两个活动对象,所以,m1和m2的作用域链是指向不同的A的活动对象的。

到这里先回顾一下前面说到的知识点:

执行环境和变量对象在运行函数时生成执行环境中的所有代码执行完以后,执行环境被销毁,保存在其中的变量和函数也随之销毁;(全局执行环境到应用退出时销毁)

难点二:判断变量对象中变量的值

function A(){    var funs=[];    for(var i=0;i<10;i++){       funs[i]=function(){           return i;       }    }    return funs; }var funs = A();//定义funs[0]-funs[9],10个函数console.log(funs[0]());//10console.log(funs[1]());//10console.log(funs[6]());//10

这个例子其实算是一个经典案例,在很多地方都有提到,执行完毕后 funs数组中,funs[0]-funs[9]存的其实都是一样的,都是一个返回i值的函数,这个例子容易错误的地方其实在于,弄错了产生执行环境的时机,还是看这句话

执行环境和变量对象在运行函数时生成

所以,当执行var funs = A();时,只是定义函数,而没有执行,真正产生环境变量的时间是在console.log(funs0);这三句的时候,此时A的变量对象中i值是什么呢?很简单,看它return的时候,i的值,显然,i的值是10,所以,最后三句输出的都是10

针对以上的案例,如果我就是想让fun[i]能够返回i,那应该怎么写呢?

首先,先来看看function anonymous1(num){}(i),这是一个立即执行函数,效果和名字一样,定义完之后马上运行结果,那这里运行的结果是什么呢?就是把i的值立即传递给num这个局部变量,然后再返回anonymous2,请注意这个立即执行函数被执行的次数,10次,再来看看这句话

执行环境和变量对象在运行函数时生成

原文链接

0 0
原创粉丝点击