前言##

在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
<script>
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
</script>

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

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
<script>
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]());//10
console.log(funs[1]());//10
console.log(funs[6]());//10
</script>

这个例子其实算是一个经典案例,在很多地方都有提到,执行完毕后 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函数使用

小结##

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

详解js中的闭包的更多相关文章

  1. 详解js中的寄生组合式继承

    寄生组合式继承是js中最理想的继承方式, 最大限度的节省了内存空间. js中的寄生组合式继承要求是: 1.子对象有父对象属性的副本, 且这些不应该保存在子对象的prototype上.       2. ...

  2. 详解JS中DOM 元素的 attribute 和 property 属性

    一.'表亲戚':attribute和property 为什么称attribute和property为'表亲戚'呢?因为他们既有共同处,也有不同点. attribute 是 dom 元素在文档中作为 h ...

  3. 详解Js中文件读取机制

    前言,文件读取是提高应用体验度的必须接口,应用场景中需求很频繁. Js处理文件读取,由于处于安全方面的考虑,在2000年以前,都是以“<input type="file"&g ...

  4. 详解javascript中的闭包

    全局变量与局部变量 在说闭包之前先说明全局变量与局部变量 全局变量:变量声明时如果不使用 var 关键字,那么它就是一个全局变量,即便它在函数内定义. 局部变量:使用var关键字定义 全局变量/局部变 ...

  5. 详解js中的apply与call的用法

    前言 call 和 apply 都是为了改变某个函数运行时的 context 即上下文而存在的,换句话说,就是为了改变函数体内部 this 的指向.call 和 apply二者的作用完全一样,只是接受 ...

  6. 详解JS中Number()、parseInt()和parseFloat()的区别

    三者的作用: Number(): 可以用于任何数据类型转换成数值: parseInt().parseFloat(): 专门用于把字符串转换成数值: 一.Number( ): (1)如果是Boolean ...

  7. 详解 JS 中 new 调用函数原理

    JavaScript 中经常使用构造函数创建对象(通过 new 操作符调用一个函数),那在使用 new 调用一个函数的时候到底发生了什么?先看几个例子,再解释背后发生了什么. 1)看三个例子 1.1 ...

  8. 详解js中的this指向

    this指向问题是个老生常谈的问题了,现在我给大家一个例子 var obj={ bar:'Cynthia' , foo:function(){ console.log(this.bar,"w ...

  9. 详解JS中 call 方法的实现

    摘要:本文将全面的,详细解析call方法的实现原理 本文分享自华为云社区<关于 JavaScript 中 call 方法的实现,附带详细解析!>,作者:CoderBin. 本文将全面的,详 ...

随机推荐

  1. 数据结构--栈 codevs 1107 等价表达式

    codevs 1107 等价表达式 2005年NOIP全国联赛提高组  时间限制: 1 s  空间限制: 128000 KB  题目等级 : 钻石 Diamond     题目描述 Descripti ...

  2. ACdream OJ 1099 瑶瑶的第K大 --分治+IO优化

    这题其实就是一个求数组中第K大数的问题,用快速排序的思想可以解决.结果一路超时..原来要加输入输出优化,具体优化见代码. 顺便把求数组中第K大数和求数组中第K小数的求法给出来. 代码: /* * th ...

  3. C++基础笔记(二)C++对C的扩展

    Xcode创建C++项目 1.新建一个MAC工程(command line tool) 2.导入头文件 3.修改文件后缀(*.m-->*.mm) 4.修改主函数中的OC代码为C++的代码   动 ...

  4. Mecanim的Avater

    角色共用同一套动作原理 先说说为什么不同的角色可以共用同一套动作:因为导入之后,我们需要为它们每一个模型都创建一个Avater,而Avater里存储了骨骼的蒙皮信息(创建Avater时把三维软件里的蒙 ...

  5. 3D跑酷遇到的问题

    前言 项目名称:3D跑酷项目 写作目地:使用Asset Server进行多人协作开发过程中,记录遇到的问题 问题1:UIAtlas无法自动更新 描述:NGUI的Atlas图集(图片)无法自动更新 后果 ...

  6. 基于PXC的MySQL高可用环境简单部署

    PXC简介 Percona XtraDB Cluster(简称PXC集群)提供了MySQL高可用的一种实现方法. 1.集群是有节点组成的,推荐配置至少3个节点,但是也可以运行在2个节点上. 2.每个节 ...

  7. JavaScript Number 对象 Javascript Array对象 Location 对象方法 String对象方法

    JavaScript Number 对象 Number 对象属性 属性 描述 constructor 返回对创建此对象的 Number 函数的引用. MAX_VALUE 可表示的最大的数. MIN_V ...

  8. javascript中的栈结构

    1.栈的定义 栈是一种和列表类似的数据结构,可以用它来解决很多的编程问题,栈是一种高效的数据结构,因为数据只能在栈的顶端添加或者删除,所以这样的操作很快而且容易实现. 栈是一种特殊的列表,站内的元素只 ...

  9. UWP 解压 GZIP

    准备工作: 通过 NUGET 安装 Microsoft.Bcl.Compression ; 使用命名空间 using System.IO.Compression ; public static asy ...

  10. VisualStudio2013+EF6+MySql5.5环境下配置

    看院子里对EF框架和MySql的配置文章不少,但是几乎出自一篇文章的转载,而且这篇转载的文章的也比较坑爹,下面我将介绍一下我的配置过程: 第一步:安装mysql-connector-net-6.9.9 ...