闭包很简单,就是在函数定义时把函数外部的环境打包进函数内,使得在函数体内可以访问到函数体外的环境(主要是访问函数体外的变量)
// 假设这里的环境为 (1)
function(){
// 假设这里的环境为 (2)
function(){
// 假设这里的环境为 (3)
};
};
在环境(1)中只能访问到环境(1)的变量,但是环境(2)中能同时访问到环境(1)(2)中的变量。环境(3)中能同时访问到环境(1)(2)(3)中的变量。
函数在定义时,把函数体外部的环境全部打包进了函数,使得函数体内可以访问到函数体定义外面的环境。这个函数+所打包的外部环境就称之为闭包。
是这样,我也是花了好长时间才内化的闭包。主要是因为原来只写过 java ,对 js 里面函数可以直接当参数传递这个事一时半会不习惯(java8 现在也已经支持行为化参数了)。闭包的真正用处是对于那写能够把函数当成参数传来传去的语言的。这个函数是必会有一些自己的东西(能访问到的变量等),这些东西随着代码的执行,一直会被这个函数访问到,这样,这个函数就形成了一个闭包。如果此时你还把这个函数当成参数传递,那么相应的这些东西(变量等)也就一起跟着过去了(能访问到)。
三点: 1. 编程中的闭包与数学中的闭包:两者概念不同。 2. 编程中的闭包是什么?本质上是一个只有子孙域可以访问得到的公共域。(域可以看作环境、变量;JS中只有function会创建域) 3. 为什么要用闭包?复用、模块化(命名空间)、更好的描述。 关于“复用、更好的描述”可以看这个例子:https://segmentfault.com/a/1190000004589338, 关于“模块化(命名空间)”其实很好理解,localEnv的命名不会覆盖(污染)topEnv嘛!
所有的语言特性都是为了更好的描述!
你去跑一下这个栗子试试,你就知道闭包该怎么用了。
var counterGenerator = function(){
var count = 0;
return function() {
count = count + 1;
return count;
};
};
var counter1 = counterGenerator();
var counter2 = counterGenerator();
counter1(); // => 1
counter1(); // => 2
counter2(); // => 1
counter2(); // => 2
你的问题其实根本就不是不懂闭包是什么,而是不懂函数式语言如何进行抽象和封装,也就是闭包如何使用的问题。比如上面的例子,如果换成面对对象的写法你可能更容易理解。
var Counter = function(){
this.count = 0;
this.next = function(){
this.count = this.count + 1;
return this.count;
};
};
var counter1 = new Counter();
var counter2 = new Counter();
counter1.next(); // => 1
counter1.next(); // => 2
counter2.next(); // => 1
counter2.next(); // => 2
其实上面两种写法干的事情是一模一样的,只是抽象方式不一样。一种是函数式的抽象方式,把数据封装在函数闭包内,另一种是面对对象式的抽象方式,把数据封装在对象上。就那么简单。
所以闭包要怎么用?你觉得你的某一个函数需要单独给它一个盒子来装东西的时候,就给他创建一个闭包。就比如上面那个计数器的例子,每一个计数器都需要一个单独的盒子来装 count 这个值,所以我就给他创建一个闭包。
// 每次执行 counterGenerator 函数都会创建了一个闭包
// 也就是创建了最后返回的那个函数
// 以及最后返回的那个函数单独的盒子
var counterGenerator = function(){
var count = 0; // 这里是下面那个函数单独的盒子
return function() { // 这里是最后返回的函数
count = count + 1;
return count;
};
};
首先请明确语言核心和语法糖的区别,否则可以谁说这世界上所有编程语言都是机器语言的语法糖而已。闭包是函数式语言必不可少的核心之一,没有了闭包会对语言有实质性的改变,所以闭包这并不是语法糖。而 JavaScript 的恰函数式占了一半,另一半是基于原型的面向对象,反倒是 class 才是 JavaScript 的 语法糖。
我工作中用到闭包的地方主要是as3和C# lambda表达式。嘿嘿,用的就是我所阐述的这些,超出这个范围我都是把它转换成类。其实你在工作中会发现,闭包最有用的就是我说的这些,别的语言把闭包搞进来,福利也就是这些。我不主张你举例子里的那个用法的,那个在实际工作中是我尽量避免的地方,那种情况下,用类更清晰。当然,js这种。。。写的不多,嗯,俺以前用haxe,现在用typescript,同样,还是上类,嘿嘿。
这是大量项目后取舍得到的均衡点。这种搞法,根本不用知道闭包是什么,直接用就行了,学习的东西特少,代码又清晰。
至于C++,我也觉得c++的模版,类,宏都是弄出来加大心智负担的玩意,所以我现在基本不写C++代码了。重点不是你觉得怎么样?而是,你觉得怎么样而去行动,并且达到了预定的效果。
(1)
闭包这个词是从数学概念发展过来的。在数学上,比如说,区间(0,1)上有一个数列:
0.9,0.99,0.999,0.9999,0.99999 ……
这个数列中的每一个元素都在(0,1)内,但是,这个数列的极限,却并不在(0,1)内,它的极限是1。这种情况下引入的闭包定义,(0,1)的闭包是[0,1],也就是,(0,1)里的每个收敛数列的极限都在[0,1]内。
(2)
这个概念直接引入程序设计中,是这样的:
一个函数,比如说,这样的一个函数:
int foo(){ return 2; }
这个函数,无论你怎么折腾它,怎么把它复制到各个地方,它都能跑。
但是下面这个函数:
int a = 5;
int foo(){ return a + 1; }
单就这个函数本身来说:
int foo() { return a + 1; }
是个不完整的函数,单独它不能跑,它依赖于外部的一个变量 a。就好比,在(0,1)内,0.9,0.99,0.999 ... 这个数列的极限不存在一样,没有 a,这个函数就是废的。再好比,你把一个男人扔到荒岛上,他的生殖功能(函数)就是废的。这种情况下,它要成立,就需要依赖外部的一个东东,它的闭包,就包括这个函数自身,以及,它依赖的最小集合。
(3)
定义到这里,你就会发现,闭包无处不在。只要是函数就是闭包,甚至万事万物都有闭包。也就是说:一个东西起作用的最小环境就是它的闭包。这样的定义,看起来没用,其实蛮有用的,在软件设计,对指导解耦,还有正交化分解非常有用。
我觉得这个定义,才是闭包最本质的概念。
(4)
这个定义,非常本质,但是太宽泛。因为:
int a = 5;
int foo() { return a + 1; }
这个搞法太普遍了,将它称为闭包没什么意义。现在很多语言都提供了函数里面套函数的机制,比如:
void foo()
{
int a = 5;
int foo2() { return a + 1; }
}
通常特指这种场景下,函数里的函数能起作用的最小环境是闭包。这是业内对闭包采用最广的定义。但实际上,你会发现,它其实就是(2)的一个特殊的例子而已。
(5)
而 js 里的狭义的闭包,也就是 bramble 上面说的闭包,实际上强调的是,把函数本身作为对象传来传去时,自身会携带它所依赖的环境(自己携带干粮,自干五的意思),这个是“闭包”在传递层面的实现。
你不实现,传出来也没用啊!跑不了啊!
这应该是函数作为闭包的封装和传递机制,在各个语言上实现是不一样的。自干五的函数就是这个意义上的闭包。
(6)
我们回过头来,你会发现,各个主流语言的函数、方法,都有传递环境的方法。面向对象中,一个Object里的方法,自身就和这个Object绑定的。但是,并不是所有的Object都是它的方法的闭包,因为里面有很多冗余的东东,不能算最小环境,Object应该是它的方法闭包的超集。如果你手动写了一个Object,里面有一个方法A,并且没有其它方法,和冗余的字段,这个Object就是方法A的闭包。
我为什么说闭包是语法糖?
你要传递函数,需要连同它的环境一起传递,最经济的方法就是传递最小环境。你手动打包太麻烦了,编译器或者是语言,会自动帮你把环境打包,只打包最小环境,不需要的不打包。
我理解的闭包就是 函数a 的内部函数b 被函数a环境之外的变量引用的时候就创建了一个闭包
。 例如:
function a() {
var s = 0 ;
return function() {
return (++s) ;
} ;
}
var result = a () ; //这时候 result 指向了函数a内部的匿名函数
使用地方可以看下面的例子,可能不太恰当,但是应该能说明问题:
function addstr (str0) {
return function(str1) {
return function(str2) {
return str0 + ' ' + str1 + ' ' + str2 ;
} ;
} ;
}
console.log(addstr('北京')('北京')('朝阳')) ;
不过闭包可以使用对象编程来替代, 不仅解决了晦涩的语法(因为函数调用了还驻存在内存里这块我们不理解的情况下是不知道的),也比较好维护代码和简单易懂, 还有网络上都说闭包的内存回收是一个问题。所以,我是不太建议使用闭包的,也可能是我也不也不太会使用它。
//不太会用ourcoders的markdown编辑 .. 调整了半天也没有调整好 无语 ~~~~
//终于调整好了, 原来有自定义代码快。。。。。
//另外可以参见 https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Closures#Practical_closures
你可以说我学究,我傻逼。但是你要明白,现在主流语言没有一个不是聚集了一大堆工程界的顶尖人才,人家全都比你我牛逼。人家辛辛苦苦费劲心思搞出来的东西,你学都没学明白了就认定这一批在工程界最顶级人才设计出来的东西没用?难道你觉得这些顶尖的工程人才全是傻逼吗?人家费劲心思就给搞一个没什么卵用的语法糖?你觉得人家是有多闲?要知道给语言加函数式特性是没有办法不动语言核心的。我之所以喜欢写编译到JavaScript的语言,是因为只要一旦编译到其他指令式语言,我就需要带着厚厚的runtime。
你用了十几年的编程语言,说句不好听的,能覆盖几个编程语言范式?至少函数式是连门槛都没摸着过吧。