Javascript中的闭包和C#中的闭包

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 = 0; i < 10; i++)
            {
                var num = i;//就是这里
                list.Add(()=> num);
                
            }
            return list;
        }
    }

 

posted @ 2017-11-02 17:29  wall-ee  阅读(468)  评论(0编辑  收藏  举报