我们
关于闭包,一个老僧长谈的话题;js的闭包俺将的比较多了,而且很详细,俺就不说了,可以看看之前的文章;
我们来对比一下c#中的闭包和js中的闭包;
先看我们的c#代码;
static List<Action> fn0() { int result = 0; List<Action> list = new List<Action>(); for (int i = 0; i < 10; i++) { result = i + 1; //这样result相当于一个全局变量 Action action = () => { Console.WriteLine(result); }; list.Add(action); } return list; } static List<Action> fn1() { List<Action> list = new List<Action>(); for (int i = 0; i < 10; i++) { int result = i + 1; //这样相当于一个局部变量; for 循环内部的变量; Action action = () => { Console.WriteLine(result); }; list.Add(action); } return list; } static void Main(string[] args) { //这样看到了,结果全尼玛的是十滴呀; fn0().ForEach(o=>o()); Console.WriteLine("------------------"); //闭包的问题会在我们的for循环和多线程中出现id呀;效果不一般滴呀; fn1().ForEach(o => o()); }
结果可想而知;
那么对于,第二种方式,如果我们使用js代码来实现呢;(ps:js的数组可以直接存我们的函数,ps:js中函数就是对象,所以就可以存)
function show(){ var arr=[]; for(var i=0;i<10;i++){ var result=i+1; arr.push(function (){ console.log(result); }) } return arr; }; var list=show();
for(var i=0;i<list.length;i++){
list[i]();
}
结果:
出现这样的原因很简单啦,俺都不想再重复,因为js中没有块级作用域的概念;不不不不,这样说不太正确,具体的应该是在esx版本前没有,具体的俺也记不清楚了;
解决方法有很多呀;形成闭包啊,使用let 关键字呀;
让我们来看看闭包是如何形成的;
function show(){ var arr=[]; for(var i=0;i<10;i++){ var result=i+1; arr.push(function (index){ return function (){ console.log(index); } }(result)) } return arr; }; var list=show(); for(var i=0;i<list.length;i++){ list[i](); }
它借助了我们的function(最外面的function)形成了一个新的作用域(result=index),这样i=>result=>index就形成了,我们的一个独立的块级别多用于了滴呀;
方法还挺巧妙的,但是对于新的es,一个let就可以解决问题了;
总结:问题的本质:js中没有块级别作用域,通过function形成一个新的额块级别作用域;这样innerfunction 访问outerfunction的变量(参数);就形成了我们的闭包;
闭包的定义之一:这里我强调的是定义之一://函数内部可以访问函数所在的作用域;,当然它有一个明显的问题就是:内存泄漏;
再回过头来看看我们c#中代码,以为c#中是有块级别作用域的,所以,方法二就没有问题;
我们看看;维基百科上定义:
闭包:词法闭包;是引用了自由变量的函数;这个被引用的自由变量,和这个函数一同存在,即是已经离开了创造它的环境;所以。有另一种说法认为:
闭包是由函数和与其相关的引用环境组合成的实体;
var x = 1; Action action = () => { var y = 2; var result = x + y; Console.Out.WriteLine("result = {0}", result); }; action();
当你在代码调试器(debugger)里观察“action”时,会发现很有趣的事情。我们可以看到,C# 编译器为我们创建了一个 Target 类,里面封装了 x 变量:
如最上面的栗子中,我们在fn0(for中定义第一个的result),以外的地方调用它是,变量依然是存在的;这个就是我们闭包生气的地方;
何避免闭包陷阱呢?C#中普遍的做法是,将匿名函数引用的变量用一个临时变量保存下来,然后在匿名函数中使用临时变量
我们再来看一段经典的js代码:
function show(){ var n=12; return function (){ n++; console.log(n); } } var fuck=show(); fuck(); fuck(); fuck();
结果:
key point:循环嵌套引用;
同样的代码c#如何实现呢;
public class Person { public Action show() { var n = 12; return () => { n++; Console.WriteLine(n); }; } } public class Program{
static void Mian(string [] args){ Person p = new Person(); var fuck = p.show(); fuck(); fuck(); fuck();
}
}
结果:
从上面的代码我们不难看到,变量n实际上是属于函数T1的局部变量,它本来生命周期应该是伴随着函数T1的调用结束而被释放掉的,但这里我们却在返回的委托b中仍然能调用它
ps:
C# 编译器帮我们做了非常多的工作,如:自动属性、类型推断、匿名类型、匿名委托、Lamda 表达式、析构方法、await 和 sync、using、对象初始化表达式、lock、默认参数 等等,
这些东西,我们都统统称呼值为语法糖;
这里我们再加一个链接:
http://www.cnblogs.com/jujusharp/archive/2011/08/04/2127999.html