js和C#中都有闭包的概念,闭包本质上是一个对象,是指有权访问另一个局部作用域中的变量的对象(或函数,在C#中是委托)。这个对象和函数/方法有关:

在js中,闭包是由于函数引用了局部变量形成的。在C#中,是由于匿名函数(本质上是委托)的存在而产生的和js原理差不多的闭包。

要了解js中的闭包,先要了解几个概念:

作用域链:而有关如何创建作用域链以及作用域链有什么作用的细节, 对彻底理解闭包至关重要。 当某 个函数第一次被调用时,会创建一个执行环境(execution context)及相应的作用域 链,并把作用域链赋值给 一个 特殊的内部属性( 即[[ Scope]])。 然后, 使用 this、 arguments 和 其他命名参数的值来初始化函数的活动对象( activation object)。 但在作用域链中, 外部函数的活动对象始终处于第二位, 外部函数的外部函数的活动对象处于第三位,…… 直至作为作用域链终点的全局执行环境。

在函数执行过程中, 为读取和写入变量的值, 就需要在作用域 链中查找变量。 来看下面的例子。

function compare( value1, value2){
if (value1 < value2){
return -1;
}
else if (value1 > value2){
return 1;
} else { return 0;
}
}
var result = compare( 5, 10);

以上代码先创建了compare函数,又在去全局作用域中调用了它。当第一次调用它的时候,会创建一个包含this、arguments、value1、value2、的活动对象。全局执行环境的变量对象(有this、result和compare)则处于compare执行环境中作用域链上的第二位。

后台的每个执行环境都有 一个表示变量的对象—— 变量对象。 全局环境 的变量对象始终存在,而像 compare() 函数这样的局部环境的变量对象, 则只在函数执行的过程中存在。 在创建compare() 函数时, 会创建一个预先包含全局变量对象的作用域链, 这个作用域链 被保存在内部的[[ Scope]] 属性中。 当调用 compare() 函数时, 会为函数创建一个执行环境, 然后通过复制函数的[[ Scope]] 属性中的对象构建起执行 环境的作用域链。 此后, 又有 一个活动对象( 在此作为变量对象使用) 被创建并被推入执行 环境作用域链 的前端。 对于这个例子中compare() 函数的执行环境而言, 其作用域链 中包含两个变量对象: 本地活动对象和全局变量对象。 显然, 作用域链本质上是一个指向变量对象的指针列表, 它只引用但不实际包含变量对象。 无论什么时候在函数中访问一个变量时, 就会从作用域链 中搜索具有相应名字的变量。 一般来讲, 当函数执行完毕后, 局部活动对象就会被销毁, 内存中仅保存全局作用域( 全局执行环境的变量对象)。 但是, 闭包的情况又有所不同。

在另 一个函数内部定义的函数会将包含函数( 即外部函数) 的活动对象添加到它的作用域链中。这样,在内部函数中的作用域链上边保存了包含环境中的活动对象(或者叫变量对象),那么,为了能够安全的执行内部函数,外部函数的变量对象会知道内部函数执行完毕后才会被安全的销毁。

需要注意的一点是闭包只有在被真正的调用时才会执行,这也引发了另外一个本质的东西,就是闭包只会调用同一个外部变量上面的最后一个值:

"use strict";
function createFunctions() {
var result = new Array();
for (var i = 0; i < 10; i++) {
result[i] = function() {
return i;
};
}
return result;
}

上面这个代码段中的循环变量i实际上时被当作局部变量而不是块级作用域下面的一个变量,因为js没有块级作用域,同样的道理在C#中也是,C#中的循环变量也是被当成局部变量的。回到正题:

result[i]=function(){return i;}这句会形成一个闭包,当我们调用createFunctions这个函数的时候会返回这个闭包:

var arr=createFunctions();
arr这个变量引用createFunction函数的返回值,是一个函数数组,每一个数组的元素都是function(){return i;}.当查看这个数组中的元素时:console.log(arr[0]());发现不管吧arr下标改成多少都是显示的10。

这是因为每个函数的作用域链中都保存着 createFunctions() 函数的活动对象, 所以它们引用的都是同一个变量 i。 当createFunctions() 函数返回后, 变量 i 的值是 10, 此时每个函数都引用着保存变量 i 的同一个变量 对象, 所以在每个函数内部i的值都是 10。

修正这个情况的办法是让闭包立即执行:

function createFunctions() {
var result = new Array();
for (var i = 0; i < 10; i++) {
result[i] = function (num) {
return function () {
return num;
};
}(i);
} return result;
} var result = createFunctions();
console.log(result[1]());

在 重写 了 前面 的 createFunctions() 函数 后, 每个 函数 就会 返回 各自 不同 的 索引 值了。 在这 个 版本 中, 我们 没有 直接 把 闭 包 赋值 给 数组, 而是 定义 了 一个 匿名 函数, 并将 立即 执行 该 匿名 函数 的 结果 赋 给 数组。 这里 的 匿名 函数 有一个 参数 num, 也就是 最终 的 函数 要 返回 的 值。 在调 用 每个 匿名 函数 时, 我们 传入 了 变量 i。 由于 函数 参数 是按 值 传递 的, 所以 就会 将 变量 i 的 当前 值 复制 给 参数 num。 而在 这个 匿名 函数 内部, 又 创建 并 返回 了 一个 访问 num 的 闭 包。 这样一来, result 数组 中的 每个 函数 都有 自己 num 变量 的 一个 副本, 因此 就可以 返回 各自 不同 的 数值 了。

在C#中解决这个问题的办法是在块级作用域中加入一个更内部层次的作用域的变量来保存循环变量的值,这样,在闭包背后生成的类会处于块级作用域中,每次循环时都会产生一个类来保存这个值,也就是说这个值都是最新的。

 class Program
{
static void Main(string[] args)
{
var funcList= ReturnFunc();
foreach (var item in funcList)
{
Console.WriteLine(item());
}
Console.ReadKey();
} static List<Func<int>> ReturnFunc()
{
List<Func<int>> list = new List<Func<int>>();
for (int i = ; i < ; i++)
{
var num = i;//就是这里
list.Add(()=> num); }
return list;
}
}

Javascript中的闭包和C#中的闭包的更多相关文章

  1. [转]JavaScript中的匿名函数及函数的闭包

    JavaScript中的匿名函数及函数的闭包  原文地址:http://www.cnblogs.com/wl0000-03/p/6050108.html 1.匿名函数 函数是JavaScript中最灵 ...

  2. JavaScript中的匿名函数及函数的闭包(转)

    JavaScript中的匿名函数及函数的闭包  https://www.cnblogs.com/wl0000-03/p/6050108.html 1.匿名函数 函数是JavaScript中最灵活的一种 ...

  3. 深入理解JavaScript中的作用域、作用域链和闭包

    版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明.本文链接:https://blog.csdn.net/qappleh/article/detai ...

  4. 《浏览器工作原理与实践》<10>作用域链和闭包 :代码中出现相同的变量,JavaScript引擎是如何选择的?

    在上一篇文章中我们讲到了什么是作用域,以及 ES6 是如何通过变量环境和词法环境来同时支持变量提升和块级作用域,在最后我们也提到了如何通过词法环境和变量环境来查找变量,这其中就涉及到作用域链的概念. ...

  5. jQuery中的闭包和js中的闭包总结

    关于闭包的知识总结下: 一.闭包 1.定义 闭包的关键是作用域,概念是:能有读取其他函数内部的函数 使用的场景有很多,最常见的是函数封装的时候,再就是在使用定时器的时候,会经常用到; //闭包:有参数 ...

  6. 闭包在python中的应用,translate和maketrans方法详解

    python对字符串的处理是比较高效的,方法很多.maketrans和translate两个方法被应用的很多,但是具体怎么用常常想不起来. 让我们先回顾下这两个方法吧: 1.s.translate(t ...

  7. javascript随机将第一个dom中的图片添加到第二个div中去

    javascript随机将第一个dom中的图片添加到第二个div中去,此代码的是一个简单的例子,将第一个div中的五张图片中,提取随机两张显示到第二个div中. <!DOCTYPE html P ...

  8. JavaScript向select下拉框中加入和删除元素

    JavaScript向select下拉框中加入和删除元素 1.说明 a   利用append()方法向下拉框中加入元素 b   利用remove()方法移除下拉框中最后一个元素 2.设计源代码 < ...

  9. javascript 的Date 格式化, 模仿shell中date命令的格式

    原文:javascript 的Date 格式化, 模仿shell中date命令的格式 shell 中显示当前的日期 [root@localhost]$ date '+%Y-%m-%d %H:%M:%S ...

随机推荐

  1. eclipse中xml下Namespaces显示不全的解决办法

    1.问题描述: 如图,有时候编写spring相关的xml文件时,使用namepace中显示不全或者完全不显示 2.解决方法: Window —— Spring ——     Beans Support ...

  2. Spring AOP失效之谜

    每天学习一点点 编程PDF电子书免费下载: http://www.shitanlife.com/code 什么是AOP1 AOP(Aspect Oriented Programming),即面向切面编 ...

  3. eclipse打断点调试进入到class文件中,不显示变量值的解决办法汇总

    每天学习一点点 编程PDF电子书免费下载: http://www.shitanlife.com/code 问题描述:eclipse打断点调试进入到class文件中,而且监视区不显示变量结果 是由于对应 ...

  4. 【转】对random_state参数的理解

    转自:https://blog.csdn.net/az9996/article/details/86616668 在学习机器学习的过程中,常常遇到random_state这个参数,下面来简单叙述一下它 ...

  5. 关于MySQL卸载重新安装的问题

    大体上一共分为3步,那么我们就美其名曰——三步走搞定MySQL安装 为什么说3步呢,如果你非要计较说我一次就重新安装成功了,就当我没说,这些是说给那些经常安装失败的同学看的! 切记,如若不想再以后My ...

  6. php利用自定义key,对数据加解密的方法

    客户端和服务端通信时,有个场景很常见,通过一个id作为url参数来回传递.假设现在业务上只有这个id标识,那么需要稍微安全一点的通信,对这个id进行加密传输,到服务端再进行解密.这里需要一个服务端进行 ...

  7. 图解IIS8上解决网站第一次访问慢的处理(转载)

    本篇经验以IIS8,Windows Server 2012R2做为案例.IIS8 运行在 Windows Server 2012 and Windows 8 版本以上的平台上.IIS中应用程序池和网站 ...

  8. 洛谷 4823 [TJOI2013]拯救小矮人

    题目链接-> 噔楞 题解: 贪心 按个高+臂长排序. 个矮臂长的先走,个高臂短的后走 #include <cstdio> #include <cstring> #incl ...

  9. 阿里Java面经大全(整合版)

    本文里的面经内容全部来源于牛客网,作为秋招备战复习与查缺补漏时使用.里面部分面经有我的注释和想法,以及部分解答,不一定正确,大家可以查询补充. 阿里巴巴,三面,java实习 昨天晚上11点打电话来,问 ...

  10. 大数据处理过程核心技术ETL详细介绍

    架构挑战 1.对现有数据库管理技术的挑战. 2.经典数据库技术并没有考虑数据的多类别(variety).SQL(结构化数据查询语言),在设计的一开始是没有考虑到非结构化数据的存储问题. 3.实时性技术 ...