浅谈JavaScript中闭包

引言

  闭包可以说是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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/399430.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

WordPress 查询数据库 操作数据库

https://www.168seo.cn/jianzhan/wordpress/advance-wordpress-tutorial/24692.html 插件位置 需要注意的是&#xff0c;由于在WordPress中可以配置wp-content/plugins/目录的位置&#xff0c;所以你必须使用plugin_dir_path()和plugins_url()两个函数来获取插件的路径。 Wor…

std string与线程安全_C++标准库多线程简介Part1

Part1:线程与互斥量本篇文章将简单的介绍一下C的标准线程库&#xff0c;本篇内容十分基础&#xff0c;如果你有C多线程相关的使用经验或者知识&#xff0c;就不必在这篇文章上浪费时间了...如果你认为本篇文章对你有帮助&#xff0c;请点赞&#xff01;&#xff01;&#xff01…

secilog 1.17 发布 增加了英文版本等新功能

2019独角兽企业重金招聘Python工程师标准>>> 日志分析软件 secilog 1.17发布&#xff0c;增加了英文版本&#xff0c;对日志导入增加了日志机器ip和日志机器名&#xff0c;或者日志ip和日志域名的关系。上篇文章1.16&#xff0c;有兴趣可以了解一下。本次升级主要增…

用clipboard.js实现纯JS复制文本到剪切板

以前很多人都是用ZeroClipboard.js来实现网页复制内容&#xff0c;火端也是用它。ZeroClipboard是利用flash来实现的&#xff0c;ZeroClipboard兼容性很好&#xff0c;但是由于现在越来越多的浏览器不支持flash&#xff0c;导致一些没法正常使用了。 今天火端开始使用clipboard…

vbs获取程序窗体句柄_PyQt5 GUI程序的基本框架

本节先通过一个简单的示例程序介绍PyQt5 GUI应用程序的基本框架。启动Python自带的编程和交互式环境IDLE&#xff0c;点击“File”→“New File”菜单项&#xff0c;打开一个文件编辑窗口&#xff0c;在此窗口中输入下面的程序&#xff0c;并保存为文件demo2_1Hello.py&#xf…

【学神】1-4 用户及用户组管理

用户及用户组管理本节所述内容&#xff1a;用户和组的相关配置文件管理用户和组 进入单用户模式找回root身份一、用户和组的相关配置文件1.用户的分类&#xff08;1&#xff09;根据帐号的位置&#xff1a;本地帐号、远程&#xff08;域&#xff09;帐号、LDAP&…

阿里REDIS优化

转载于:https://www.cnblogs.com/isoftware/p/3745956.html

vb子程序未定义怎么改怎么办_煤粉输送不畅、秤不稳怎么办?多家企业这样改!...

摘要: CWF型煤粉转子喂料系统优点在于运行稳定、精度高&#xff0c;一致以来受到用户的青睐&#xff0c;但其对煤粉要求也高&#xff0c;尤其是水分含量&#xff0c;直接影响给料的稳定性和受控性。各种非正常因素对测控的影响较大,容易造成下料不畅、波动等现象&#xff0c;下…

SQL Server实现读写分离提高系统并发

转自&#xff1a;http://www.canway.net/Lists/CanwayOriginalArticels/DispForm.aspx?ID666 在一些大型的网站或者应用中&#xff0c;单台的SQL Server 服务器可能难以支撑非常大的访问压力。很多人在这时候&#xff0c;第一个想到的就是一个解决性能问题的利器——负载均衡。…

php如何获取网址中的参数

https://zhidao.baidu.com/question/140978452.html 比如有一个网址为 http://域名/goods.php?u59&id24#pinglun 我想得到这个id值 可以用正则,也可以用php函数解析到数组中 用正则可以这样 preg_match(/id(\d)/,$_SERVER["REQUEST_URI"],$m);//$_SERVER 这…

.net core 引用jar_Python一键转Jar包,Java调用Python新姿势!

粉丝朋友们&#xff0c;不知道大家看故事看腻了没&#xff08;要是没腻可一定留言告诉我^_^&#xff09;&#xff0c;今天这篇文章换换口味&#xff0c;正经的来写写技术文。言归正传&#xff0c;咱们开始吧&#xff01;今天的这篇文章&#xff0c;聊一个轩辕君之前工作中遇到的…

PHP版本的区别

2019独角兽企业重金招聘Python工程师标准>>> 以为这个已经写过了&#xff0c;发现没有&#xff0c;赶紧补充下。 PHP的版本&#xff0c;自从进入5以后&#xff0c;发布新版本速度明显提升很多&#xff0c;从PHP5.2开始&#xff0c;5.3 、5.4 、5.5&#xff0c;就快要…

1515 跳 - Wikioi

题目描述 Description邪教喜欢在各种各样空间内跳。现在&#xff0c;邪教来到了一个二维平面。在这个平面内&#xff0c;如果邪教当前跳到了(x,y)&#xff0c;那么他下一步可以选择跳到以下4个点&#xff1a;(x-1,y), (x1,y), (x,y-1), (x,y1)。而每当邪教到达一个点&#xff0…

快速格式化的DOS命令是什么?

https://zhidao.baidu.com/question/3269005.html Dos下的format命令是用来进行格式化的。 FORMAT A:或c:, d:等等&#xff0c;后面可以加一些参数 比如FORMAT A: /S 这是格式化并追加系统。也就是制作一个简单的启动盘。 如果给硬盘进行格式化&#xff08;重装系统前的准备工…

ADT-bundle

eclipse 弹出 Version 1.4.2_03 of the JVM not suitable for this product.Version1.6or geeater is requir 原因jdk版本过低&#xff0c;需更换高版本 ADT-bundle-linux-X86 解压后有两个文件夹eclipse 和sdk 注意&#xff1a;adb命令在platform-tools下 解压后还需修改sdk的…

合并两个有序链表算法(leetcode第21题)

题目描述&#xff1a; 将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。 示例 1&#xff1a;输入&#xff1a;l1 [1,2,4], l2 [1,3,4] 输出&#xff1a;[1,1,2,3,4,4] 示例 2&#xff1a;输入&#xff1a;l1 [], l2 [] 输…

修改maven本地仓库位置

2019独角兽企业重金招聘Python工程师标准>>> 首先需要安装好&#xff0c;假设安装位置在&#xff1a;D:\****\maven\apache-maven-3.3.3修改本地仓库位置&#xff0c;打开配置文件&#xff1a;D:\****\maven\apache-maven-3.3.3\conf\settings.xml<!-- localRepo…

iOS sqlite

iOS sqlite数据库操作。步骤是&#xff1a; 先加入sqlite开发库libsqlite3.dylib&#xff0c; 新建或打开数据库&#xff0c; 创建数据表&#xff0c; 插入数据&#xff0c; 查询数据并打印 1、新建项目sqliteDemo,添加使用sqlite的库libsqlite3.dylib 2、sqlite 的方法 sqlite…

ad如何设置pcb板子形状_手把手教你在PCB上添加泪滴

泪滴(Teardrops)在PCB电路板设计中&#xff0c;为了让焊盘更坚固&#xff0c;防止机械制板时焊盘与导线之间断开&#xff0c;常在焊盘和导线之间用铜膜布置一个过渡区&#xff0c;形状像泪滴&#xff0c;故常称做补泪滴&#xff08;Teardrops&#xff09;。泪滴的作用避免电路板…

华为云hcip认证试题_首信AAA认证计费系统通过华为云兼容性认证,成为华为认证级ISV伙伴...

点击上方“蓝字”关注我们首信AAA认证计费系统软件3.0在2020年9月24日通过华为云Stack 6.5(鲲鹏)的兼容性测试&#xff0c;最终获得华为技术认证书及HUAWEI COMPATEBLE相关认证徽标使用权。同时&#xff0c;首信科技获得华为认证级ISV伙伴认证证书。本次获得的产品兼容性认证及…