博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
【转】再说一说闭包
阅读量:4884 次
发布时间:2019-06-11

本文共 5077 字,大约阅读时间需要 16 分钟。

原文:

初涉闭包

闭包是指有权访问另一个函数作用域变量的 函数 ,创建闭包的通常方式,是在一个函数内部创建另一个函数

上文我们提到了,由于作用域链的结构,外围函数是无法访问内部变量的,为了能够访问内部变量,我们就可以使用闭包, 

闭包的本质还是函数 , 闭包的本质还是函数 闭包的本质还是函数 。

function A(){        var x = 1;        return function(){            x++;            console.log(x);        }    }    var m1 = A();

  

上面就是一个很简单的闭包例子,通过m函数,我们可以获得A函数内部变量的值,这个样例比较简单,看不出什么问题,接下来我们来深入了解一下。

-------------------------------从简单到复杂的分割线,请做好准备----------------------------------------------------

4. 闭包详解

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

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

  

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

1.为什么连续执行m1的时候,x的值在递增?

2.定义函数m2的时候,为什么x的值重新从1开始了?

3.运行m2以后,为什么再运行m1,x还是按照之前m1的运行结果继续增长?(其实就是m1和m2里面的x为什么是相互独立,各自维持的?)

额,这图画的可能真的有点丑),不要慌,图上虽然画的有点乱,但是其实很简单:左半部分和上面简单闭包的例子,其实是完全一样的,而右边半部分,与左边其实是完全对称的;注意看图上的重点: 每次执行A函数时,都会生成一个A的活动变量和执行环境,执行完毕以后,A的执行环境销毁,但是活动对象由于被闭包函数引用,所以仍然保留 ,所以,最终剩下两个A的变量对象, 因此m1和m2在操作x时,指向的是不同的数据 ,

function A(){        var x = 1;        var m=[];        m[0] = function(){            x++;            console.log(x);        };        m[1] = function(){            x++;            console.log(x);        }         return m;    }    var m = A();//第一次运行A,而且只运行这一次    m[0]();//2    m[1]();//3    m[0]();//4    m[1]();//5

  其是还要配上灵魂画师级别的配图

--------------------------------------------------------------------------------------------------------------

详解js中的闭包

前言

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

写在闭包之前

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

1. 执行环境和活动对象

- 执行环境(execution context)定义了变量或者函数有权访问的其他数据,每个执行环境都有一个与之关联的 变量对象(variable object) ,执行环境中定义的变量和函数就保存在这个变量对象中;

全局执行环境是最外围的一个执行环境,通常被认为是window对象

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

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

2. 作用域链

当代码在一个执行环境中执行时,会创建 变量对象 的一个 作用域链(scope chain),作用域链用来指定执行环境有权访问的所有变量和函数的 访问顺序 ;

作用域链的最前端,始终是 当前代码执行环境的变量对象 ,如果这个环境是函数,则其活动对象就是变量对象

作用域链的下一个变量对象,来自外部包含环境,再下一个变量对象,来自下一个外部包含环境,以此类推直到全局执行环境

在函数执行过程,根据当前执行环境的作用域链来 逐层向外 查找变量,并且进行标识符解析

是不是觉得以上的理论很枯燥而且艰涩?因为基本上是从书上引用来的,不着急着理解,先摆在上面,等会结合案例回头再来看!接下来请看样例:

样例1

以这段简单的代码为例,根据上面的理论画一下关系图(直接用ps画的,原谅我拙劣的笔迹):

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

样例2

这个例子比较简单,要画图的话只需要画一个全局变量对即可,因为在js中,外围环境无法访问内围局部变量(其实本质就是作用域链上找不到相应的值),所以这里会报变量未定义的错误。

样例3

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

关于执行环境和作用域链暂时说到这里,下面进入正题,讲闭包;

3. 初涉闭包

闭包是指有权访问另一个函数作用域变量的 函数 ,创建闭包的通常方式,是在一个函数内部创建另一个函数

上文我们提到了,由于作用域链的结构,外围函数是无法访问内部变量的,为了能够访问内部变量,我们就可以使用闭包, 闭包的本质还是函数 , 闭包的本质还是函数 闭包的本质还是函数 。

样例4

上面就是一个很简单的闭包例子,通过m函数,我们可以获得A函数内部变量的值,这个样例比较简单,看不出什么问题,接下来我们来深入了解一下。

-------------------------------从简单到复杂的分割线,请做好准备----------------------------------------------------

4. 闭包详解

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

样例5

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

1.为什么连续执行m1的时候,x的值在递增?

2.定义函数m2的时候,为什么x的值重新从1开始了?

3.运行m2以后,为什么再运行m1,x还是按照之前m1的运行结果继续增长?(其实就是m1和m2里面的x为什么是相互独立,各自维持的?)

其实要解决上面的问题,我们就要用到前面铺垫的知识点了:

首先,先画一下结构图,

(额,这图画的可能真的有点丑),不要慌,图上虽然画的有点乱,但是其实很简单:左半部分和上面简单闭包的例子,其实是完全一样的,而右边半部分,与左边其实是完全对称的;注意看图上的重点: 每次执行A函数时,都会生成一个A的活动变量和执行环境,执行完毕以后,A的执行环境销毁,但是活动对象由于被闭包函数引用,所以仍然保留 ,所以,最终剩下两个A的变量对象, 因此m1和m2在操作x时,指向的是不同的数据 ,

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

1.(为什么连续执行m1的时候,x的值在递增?)

answer:因为m1在引用的活动对象A一直没有释放(想释放的话可以让m1=null),所以x的值一直递增。

2.定义函数m2的时候,为什么x的值重新从1开始了?

answer:因为又一次运行了A函数,生成一个新的A的活动对象,所以m2的作用域链引用的是一个新的x值。

3.m1和m2里面的x为什么是相互独立,各自维持的?

answer:因为在定义m1和m2的时候,分别运行了A函数,生成了两个活动对象,所以,m1和m2的作用域链是指向不同的A的活动对象的。

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

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

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

感觉理解了吗?接下来,再看看另一个很类似的例子:

样例6 

这个例子和刚刚十分类似,不同的是,在A内部就先定义了两个函数,可以看出 ,最后的结果与上面的例子有些不同:

变量x仍然能保持递增,但是m[0]和m[1]定义的函数,对于x的改变不再是相互独立的,其实大家估计猜到了,这里的m[0]和m[1]的作用域指向的A的变量对象,其实是同一个,为什么呢?很简单,看看刚刚这段代码,其实是只调用了一次A函数,再看上文那句话:

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

既然A只执行一次,那么A的活动变量当然也就生成了一个,所以这里m[0]和m[1]的作用域指向同一个A的变量对象

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

样例7 

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

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

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

好的,针对以上的案例,如果我就是想让fun[i]能够返回i,那应该怎么写呢?在《javascript高级程序设计》中,提供了一种参考的写法:

样例8 

是不是一看头就大了?没关系,接下来我们慢慢分析,当然,上述代码中anonymous1和anonymous2两个名字是我自己添加上的,为了后面能够更好的说明。

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

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

好的,那现在请回答我:

这里面生成了几个anonymous1的活动变量?

answer:当然也是10个,

那每个anonymous1活动变量中存贮的num值是多少?

answer:看anonymous函数return的时候可以知道,存贮的num值就是每次传入的i值,也就是0-9

好了,那现在很明了了,这样的写法其实相当于, 把每次的i值都保存在一个anonymous1活动变量钟,给最内层的anonymous2函数使用

小结

写到这里,关于闭包的主要特征和辨别方式已经基本讲到了,个人感觉因为这个问题比较抽象,还是多看看文中以及网上的一些例子,加深理解。以上内容属于个人见解,如果有不同意见,欢迎指出和探讨。希望能对看到的人有所帮助,同时,码字不易(尤其是还要配上灵魂画师级别的配图~),请尊重作者的版权,转载请注明出处,如作商用,请与作者联系,感谢!

转载于:https://www.cnblogs.com/oxspirt/p/6031103.html

你可能感兴趣的文章
如何在 Xcode 中修改应用的名字
查看>>
有关交换机——熟悉原理是必须的【转载】
查看>>
ACM(数学问题)——UVa202:输入整数a和b(0≤a≤3000,1≤b≤3000),输出a/b的循环小数表示以及循环节长度。...
查看>>
【转】Android 读取doc文件
查看>>
js 数据绑定
查看>>
jsp的C标签一般使用方法以及js接收servlet中的对象及对象数字
查看>>
window.frameElement的使用
查看>>
如何使用jQuery $.post() 方法实现前后台数据传递
查看>>
Using Flash Builder with Flash Professional
查看>>
jsp/post中文乱码问题
查看>>
C# 插入或删除word分页符
查看>>
数据库数据的查询----连接查询
查看>>
找不到可安装的ISAM ,asp.net读取数据丢失,解决的一列里有字符与数字的
查看>>
Java学习笔记三(对象的基本思想一)
查看>>
Java程序(文件操作)
查看>>
KMP算法 最小循环节 最大重复次数
查看>>
Proving Equivalences (强连通,缩点)
查看>>
Period (KMP算法 最小循环节 最大重复次数)
查看>>
sgu 103. Traffic Lights
查看>>
poj 3621 Sightseeing Cows
查看>>