引言

  闭包可以说是JavaScript中最有特色的一个地方,很好的理解闭包是更深层次的学习JavaScript的基础。这篇文章我们就来简单的谈下JavaScript下的闭包。

闭包是什么?

  闭包是什么?通俗的解释是:有权访问另一个函数作用域中变量的函数。创建闭包的常见方式,就是在一个函数内部创建另一个函数(作为其子函数)。下面我们还是以前面的一个例子来简单介绍下:

 1 //通过属性名称来对数组元素进行排序
 2     function createComparisonFunction(propertyName) {
 3         return function (obj1, obj2) {
 4             var val1 = obj1[propertyName];
 5             var val2 = obj2[propertyName];
 6             if (val1 < val2) {
 7                 return -1;
 8             }
 9             else if (val1 > val2) {
10                 return 1;
11             }
12             else {
13                 return 0;
14             }
15         }
16     }

  我们看的在这个函数中我们定义了一个匿名函数,并且将匿名函数作为值返回。注意代码地4、5行,这两行代码访问了外部函数中的变量propertyName。即使这个函数返回了,或者在其他地方被调用了,我们通过这个匿名函数仍然可以访问这个变量。这是为什么呢?想想前面在对象内部搜索属性的机制。很明显,匿名函数的作用域链包含了createComparisonFunction的作用域链。这又是为什么呢?还得从函数被第一次调用发生的一些细节上进行讨论。

 函数第一次调用到底发生了什么

  之前介绍作用域链的博客中有关于这方面内容的介绍。相信大家肯定还记忆犹新。当一个函数第一次被调用的时候,会创建一个执行环境和作用域链,并把作用域链赋值给一个特殊的内部属性[Scope]。然后使用this、arguments和其他命名参数的值来初始化函数的活动对象。外部函数的活动对象位于第二位,外部函数的外部函数的活动对象在第三位,直到作为作用域链终点的全局执行换环境。

  下面还是通过一个简单的例子来重温下这方面的内容:

 1 function compare(value1, value2) {
 2         if (value1 < value2) {
 3             return -1;
 4         } else if (value1 == value2) {
 5             return 0;
 6         }
 7         else {
 8             return 1;
 9         }
10     }
11
12     var result = compare(5, 10);

  那么,按照我们之前的描述。在执行第12行代码的时候,作用域链相关的分析应该是这样的。看图:

  后台的每一个执行环境都有一个表示变量的对象--变量对象。全局环境的变量对象始终存在,像compare函数这样的局部环境的变量对象,只是在运行时存在。在创建compare()函数的时候,会创建一个预先包含全局变量对象的作用域链。这个作用域链被包含在内部的[Scope]属性中。当调用compare()函数的时候,会为函数创建一个执行环境,然后通过复制函数的[Scope]属性中的对象构建起执行环境的作用域链。

  无论什么时候在函数中访问一个变量时,就会从作用域链中搜索具有相应名字的变量。一般来讲,当函数执行完毕以后,局部变量对象就会被销毁,内存中只保存全局作用域。但是闭包让情况变的有点不同。

  下面我们来看下我们最开始的那个例子:

1 var compare = createComparisonFunction("name");
2 var result = compare({ name: "Nicolas" }, { name: "Grey" });

  在另一个函数内部会将包含函数(外部函数)的活动对象添加到它的作用域链中。因此在createComparisonFunction函数内部定义的匿名函数的作用域链中会将createComparisonFunction函数的变量对象包含在自己的作用域链中。下面这张图很好的展示了这一点:

  匿名函数的作用域链中引用了外部函数的变量对象(活动对象)。但是:createComparisonFunction函数执行完以后,其活动对象也不会被销毁。因为匿名函数的作用域链中还引用着createComparisonFunction的活动对象。我们也可以这样认为,createComparisonFunction函数执行完以后,其作用域链被销毁,但是其活动对象仍然在内存中。所以,过度的使用闭包可能会导致内存占用过高。

  闭包与变量

  作用域链的这种配置机制导致了一个副作用,即闭包只能取得包含函数中任意变量的最后一个值。因为闭包通过作用域链引用的是整个变量对象。外部函数的变量存储在其变量对象中。下面的例子可以展示这个问题:

 1 /**
 2      * 闭包与变量的关系示例
 3      **/
 4     function createFunctions() {
 5         var result = [];
 6         for (var i = 0; i < 10; i++) {
 7             result[i] = function () {
 8                 return i;
 9             }
10         }
11         return result;
12     }
13
14     var funs = createFunctions();
15     for (var i = 0; i < 10; i++) {
16         alert(funs[i]());           //输出10次10
17     }

  我们看到每一个函数都输出10。并不是我们想象中的1-10之间的数值。因为每一个result数组引用的匿名函数内部都包含了createFunctions函数的活动对象。循环每一次的调用,修改的都是createFunctions变量对象中的i值。最后我们调用的时候看到的只是最后的一个i的值。那么我们怎么修改,才能按预想的输出1-10呢。问题的关键在于:我们如果能每一次循环的时候把i的值预存起来不就可以了吗?看看下面的这个改进方案:

 1 function createFunctions() {
 2         var result = [];
 3         for (var i = 0; i < 10; i++) {
 4             result[i] = (function (argument) {
 5                 return function () {
 6                     return argument;
 7                 }
 8             })(i);
 9         }
10         return result;
11     }
12
13     var funs = createFunctions();
14     for (var i = 0; i < 10; i++) {
15         alert(funs[i]());           //输出1-9
16     }

  我们通过改进后,终于如愿的输出了1-9。看看到底发生了什么?在代码的第4-8行,我们看到我们创建了一个匿名函数,并且将i的值作为参数传递给它,然后立即执行这个匿名函数。这个匿名函数内部返回了另一个匿名函数,result数组中保存的匿名函数的作用域链里面就会有4个活动对象,分别是本身的活动对象、外部匿名函数(已执行)的活动对象(包含传递的i的值,即argument)、createFunctions的活动对象、全局活动对象。下面我们在执行返回的匿名函数时,通过作用域链来搜索到argument变量。每一个argument变量都是当时执行时传递的i的值。

  关于this对象

  在闭包中使用this值也会导致一些问题。this对象是在运行时根据函数的执行环境绑定的。在全局执行环境中,this等于window,而当函数作为某一个对象的方法调用时,this等于那个对象。匿名函数的执行环境具有全局性,this的值通常等于window。但有时候,可能由于编写闭包的方式不同,这一点可能不会那么明显。比如下面的例子:

 1 var name = "The Window";
 2     var object = {
 3         name: "The Object",
 4         getNameFun: function () {
 5             return function () {
 6                 return this.name;
 7             }
 8         }
 9     }
10
11     alert(object.getNameFun()());   //输出The Window

  按照之前在作用域链中搜索变量的机制。输出应该是The Object才对。但是为什么是The Window呢?前面应该提到过,每个函数在被调用时,其活动对象都会自动获取两个变量this和arguments。内部函数在搜索这两个变量的时候,只会搜索到其活动对象为止,因此,无法访问外部函数的这两个变量。不过通过简单的修改我们可以实现弹出The Object的效果。请看下面的例子:

 1 var name = "The Window";
 2     var object = {
 3         name: "The Object",
 4         getNameFun: function () {
 5             var that = this;
 6             return function () {
 7                 return that.name;
 8             }
 9         }
10     }
11
12     alert(object.getNameFun()());   //输出The Object

  我们在返回匿名函数之前,将this保存在that变量中,作为闭包,最深层次的匿名函数在调用时,其作用域链中会包含getNameFun这个函数的活动对象。因此这时that还是引用object对象。我们能正常的弹出The Object。讲到这里,相信大家对闭包都有一个详细的了解了把。最后推荐大家看个网页,里面有很多经典的闭包的事例哦。http://www.oschina.net/question/28_41112。

转载于:https://www.cnblogs.com/dreamGong/p/4931570.html

浅谈JavaScript中闭包相关推荐

  1. 浅谈javascript中原型(prototype)、构造函数、对象实例及三者之间的关系

    转自:http://www.cnblogs.com/zhangwei412827/archive/2012/12/14/2816263.html 浅谈javascript中原型(prototype). ...

  2. swift 引用其他类_浅谈swift中闭包修饰符 weak?unowned? 或什么都不用

    浅谈swift中闭包修饰符 weak?unowned? 或什么都不用 平常的开发中,clourse是我们iOSr绕不过去的坎儿. 苹果本身也很重视闭包,像之前的一些老的target-action类型的 ...

  3. html 滚动条 scrolltop scrollheight,浅谈JavaScript中scrollTop、scrollHeight、offsetTop、offsetHeight...

    浅谈JavaScript中scrollTop.scrollHeight.offsetTop.offsetHeight 发布时间:2020-07-17 09:27:20 来源:亿速云 阅读:223 作者 ...

  4. 浅谈JavaScript中的NaN

    浅谈JavaScript中的NaN NaN概念以及简单案例 追寻的纯粹该拥有自己的本质.-JC.F 什么是NaN? NaN:NaN(Not a Number),它表示不是数字,但是仍是数值类型. Na ...

  5. 浅谈Javascript中的void操作符

    由于JS表达式偏啰嗦,于是最近便开始采用Coffeescript来减轻负担.举个栗子,当我想取屋子里的第一条dog时,首先要判断house对象是否存在,然后再判断house.dogs是否存在,最后取h ...

  6. html dom节点类型,浅谈Javascript中的12种DOM节点类型

    前言 DOM的作用是将网页转为一个javascript对象,从而可以使用javascript对网页进行各种操作(比如增删内容).浏览器会根据DOM模型,将HTML文档解析成一系列的节点,再由这些节点组 ...

  7. 浅谈JavaScript中的事件

    事件在javascript中是响应用户的一种基本操作,本文列举了两种javascript中的事件模型及其绑定的方式,希望能对你的学习带来一点帮助.这些都是非常基础的但或许其中有你所遗漏.(以下事件均针 ...

  8. 浅谈JavaScript中的对象和类型(上)

    JavaScript是一种不同于任何强类型程序设计语言的脚本语言,这决定了它对于许多强类型语言的程序员来说有很多莫名其妙.难以理解的地方,本文是本人对JavaScript的一些实践总结出来的简单易懂的 ...

  9. 浅谈Javascript中的函数重载实现

    其它面向对象语言如Java的一个常见特性是:能够根据传入的不同数量量或类型的参数,通过"重载"函数来发挥不同的功用.但是这个特性在Javascript中并没有被直接支持,可是有很多 ...

最新文章

  1. 关于 iOS 10 中 ATS 的问题
  2. python中matrix是什么意思_初识Python
  3. python bootstrap-fileinput示例_bootstrapfileinput实现文件自动上传
  4. 锅巴H264播放器地址和说明
  5. [转载]golang sync.Pool
  6. mysql 单数据库设置编码,mysql数据库编码设置
  7. Android Device Monitor 的 File Explorer 打开一片空白,不显示文件
  8. java mina 大文件传输_mina 传输java对象
  9. CENTOS 使用 MUTT发送邮件
  10. 常见的SQL优化面试题
  11. CRC循环冗余校验码原理解析(附实例)
  12. 阅读心得1:《蚂蚁金服11.11:支付宝和蚂蚁花呗的技术架构及实践 》
  13. 无线充电器的CE认证、FCC认证、IC认证测试标准
  14. 为什么结婚戒指要戴在无名指
  15. 扩散模型又杀疯了!这一次被攻占的领域是...
  16. SpringData Manytomany 中间表添加额外字段
  17. Gvim插件NERDTree安装
  18. [转贴]Debian的汉化步骤【转自http://www.linuxsir.org】
  19. 响应式织梦模板民宿景区旅游类网站
  20. linux PWM驱动屏幕亮度及pwm子系统框架(Linux驱动开发篇)

热门文章

  1. php常量变量连接,PHP常量及变量区别原理详解
  2. python函数与模块学习_Python函数与模块学习1
  3. 导出文件_一招解决PDF文件导出图片
  4. LeetCode(集合)队列和栈的相互实现 golang
  5. 540. 有序数组中的单一元素 golang
  6. 软件工程学习笔记《目录》
  7. 比较ArrayList和数组的区别
  8. 从功能层次,阐述CPU、接口和外设之间的交互
  9. TCP粘包问题分析和解决(全)
  10. 程序随笔——C++实现的一个线程池