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. php一致性hash算法

    原理部分转自:https://www.jianshu.com/p/e8fb89bb3a61 基本场景 比如你有 N 个 cache 服务器(后面简称 cache ),那么如何将一个对象 object ...

  2. Excel中sumproduct函数的使用方法和用途

    上表是公司人员,所属工段,年龄,工资等信息.现在要统计每个工段所有人员的工资总和.   从拆解工段开始.输入=SUMPRODUCT(($B$2:$B$9=A12)*($D$2:$D$9)); $B$2 ...

  3. document.querySelector获取不到html标签对象实例的原因

    官方给出的HTML中的ID的命名规范: 1.必须以字母 A-Z 或 a-z 开头2.其后的字符:字母(A-Za-z).数字(0-9).连字符("-").下划线("_&qu ...

  4. sysbench数据库压力测试

    sysbench是一款压力测试工具,可以测试系统的硬件性能,也可以用来对数据库进行基准测试 wget https://github.com/akopytov/sysbench/archive/1.0. ...

  5. node.js 基础三 消息推送

  6. php实现一个简单的四则运算计算器

    php实现一个简单的四则运算计算器(还不支持括号的优先级).利用栈这种数据结构来计算表达式很赞. 这里可以使用栈的结构,由于php的数组“天然”就有栈的特性,这里直接就利用了数组.当然可以使用栈结构写 ...

  7. CMD命令查看当前电脑安装所有版本.NET Core SDK(转载)

    dotnet --version 查看当前使用版本 dotnet --info 安装的所有版本 包括版本地址 也可用命令帮助 dotnet help 原文链接

  8. Luogu2336 SCOI2012 喵星球上的点名 SA、莫队

    传送门 一道很套路的题目 先将所有串拼在一起,两个不同的串之间放一个没有出现在任何串中的字符做分隔,然后SA 那么对于所有点名串能够点到的名字串在SA中对应一段区间 把这些区间拿出来然后莫队统计每一个 ...

  9. 10个Chrome基础使用技巧

    Chrome是前端开发中最常用到的一个浏览器,本文整理了Chrome的10个基础使用技巧(Chrome中有很多和Sublime Text2类似的快捷键).如果有其它本文未提及的实用的小技巧,也可以留言 ...

  10. 【转】分布式一致性算法:Raft 算法(Raft 论文翻译)

    编者按:这篇文章来自简书的一个位博主Jeffbond,读了好几遍,翻译的质量比较高,原文链接:分布式一致性算法:Raft 算法(Raft 论文翻译),版权一切归原译者. 同时,第6部分的集群成员变更读 ...