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. Arduino 433 自定义接受

    /* Simple example for receiving https://github.com/sui77/rc-switch/ */ #include <RCSwitch.h> # ...

  2. go标准库的学习-encoding/xml

    参考:https://studygolang.com/pkgdoc 导入方式: import "encoding/xml" 实现的简单的理解XML命名空间的XML 1.0编译器 f ...

  3. Java内存分配之堆、栈和常量池(转)

    摘录自http://www.cnblogs.com/SaraMoring/p/5687466.html Java内存分配主要包括以下几个区域: 1. 寄存器:我们在程序中无法控制 2. 栈:存放基本类 ...

  4. 深入浅出的webpack构建工具---devTool中SourceMap模式详解(四)

    阅读目录 一:什么是SourceMap? 二:理解webpack中的SourceMap的eval,inline,sourceMap,cheap,module 三:开发环境和线上环境如何选择source ...

  5. PAT A1012 The Best Rank (25 分)——多次排序,排名

    To evaluate the performance of our first year CS majored students, we consider their grades of three ...

  6. MySQL(九)插入、更新和删除

    常用的SQL语句,除了select用于查询,还有insert.update.delete等. 一.insert insert:用来插入(或添加)行到数据库中,常见方式有以下几种: ①插入完整的行: ② ...

  7. Volume is already attached by pod default/nginx-deployment-86dfb99868-szpkd. Status Running

    1.部署WordPress - mysql ,想扩容,修改deployment,结果报错: MountVolume.SetUp failed for volume "pvc-e" ...

  8. wifidog源码分析 - wifidog原理

    wifidog是一个用于配合认证服务器实现无线网页认证功能的程序,常见的情景就是使用于公共场合的无线wifi接入点,首先移动设备会连接公共wifi接入点,之后会弹出网页要求输入用户名密码,认证过后才能 ...

  9. GIT 远程仓库:添加远程库、从远程库克隆

    到目前为止,我们已经掌握了如何在Git仓库里对一个文件进行时光穿梭,你再也不用担心文件备份或者丢失的问题了. 可是有用过集中式版本控制系统SVN的童鞋会站出来说,这些功能在SVN里早就有了,没看出Gi ...

  10. Luogu1514 NOIP2010 引水入城 BFS、贪心

    传送门 NOIP的题目都难以写精简题意 考虑最上面一排的某一个点对最下面一排的影响是什么样的,不难发现必须要是一段连续区间才能够符合题意. 如果不是一段连续区间,意味着中间某一段没有被覆盖的部分比周围 ...