关于闭包一大堆问题

2015-12-21 15:37:10 +08:00
 Mark24

老生常谈
1.闭包是什么?
2.闭包有什么用?

3.附加题 JS 的闭包和 Python3 的闭包各有什么特点?

想吐槽

4.大多对于闭包的解释,都是:
a.从结果出发(黑盒)
b.从底层原理出发(实现)
实际上,我从未看过这样子解释一个东西,这样解释,缺乏对闭包的深刻了解。我相信,即使你懂了,你也不知道如何去创造性的使用闭包,仅仅是复制前人的例子而已。
不清不楚的东西,要不就是遗忘,要不就是从来不用。

看了这么多 blog 和资料,没有一个能够解答心中的疑惑的,郁闷呢……

5.闭包真的是一个好的设计么?
很多资料都是表示,自由变量,可以常驻内存,过量使用,会导致内存泄漏,所以谨慎使用
额……为毛追捧一个,会故意导致内存泄漏的特性
还夸上天……我怎么觉得呵呵
这是明显的设计漏洞啊

对 JS 各种黑科技深深的不服,设计的漏洞百出。
我倾向于,这是一个漏洞,历史,或者被误用而夸大的特性
而不是经过深深的思考,释放出来的特性
2015 次点击
所在节点    问与答
11 条回复
SpicyCat
2015-12-21 16:15:56 +08:00
建议看看这个了解下 closure 。
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Closures

如果没有 closure, JS 将一文不值。所谓有失必有得,灵活性的代价就是稳定性。
banricho
2015-12-21 16:51:33 +08:00
JS 的闭包涉及到作用域的特性,这两天也整理了一下,算是对 MDN 中例子的补充。
欢迎讨论,欢迎指正,相互学习~

https://github.com/banricho/webLog/issues/4#issuecomment-165320894
YuJianrong
2015-12-21 16:58:10 +08:00
不懂就不懂唧唧歪歪半天,还内存泄露……
你造一个 array 只添不删也是内存泄露知道吗!(而且所谓闭包的内存泄露就和这个差不多)

闭包是个 60 年代就创造 70 年代就实现的语言概念,现在 C++ 11 , ObjectiveC , Java8 都支持或者开始支持的概念你说是明显的设计漏洞?

呵呵。
yangtze
2015-12-21 16:59:37 +08:00
闭包还是很好用的,产生原因是 ES5 之前 javascript 仅支持全局作用域和函数作用域,而没有块级作用域

不过 ES6 中对全局作用域这块添加了另一种解决方案,把 var 改成 let , function 改成 class ,就有块级作用域了,基本就不要专门搞闭包了
ChefIsAwesome
2015-12-21 17:02:44 +08:00
知道多少语言有闭包这个概念吗
banricho
2015-12-21 17:11:08 +08:00
@yangtze

我认为 let 只是方便了开发,避免了部分情况下手动创建闭包。从下面的例子来看,即使用了 let 还是创建了很多个闭包。

function demo() {
'use strict';

var links = document.getElementsByTagName('a'),
len = links.length;

for (let i = 0; i < len; i++) {
links[i].onclick = function() {
console.log(i);
return false;
}
}
}

闭包本身不是罪,内存就是拿来用的,合理使用就行…
Mark24
2015-12-21 23:45:22 +08:00
@banricho 感谢整理,感谢回答。给了我很大灵感

我还是自问自答吧
刚才又看了一会资料。个人的粗糙理解:

以 Python3 的闭包为例:
在我理解,闭包的最大意义就在于
1.保存环境:实际上创造了第三种作用域,内部的变量可以不被回收
2.函数的面向对象:将函数和数据捆绑了起来,第三种作用域内部的函数可以看出私有变量(因为别人访问不到)
a.包装了函数 b.捆绑了变量数据

顺便区分一下,装饰器,偏函数,闭包
函数式编程,这三者真的是纠缠在一起,没搞清楚就会混淆

装饰器,偏函数其实并不是闭包
他们也基本上没有什么关系
他们只是形式上有些相似,就是函数嵌套函数,并且返回函数

把函数当做参数,和当成返回值,这就叫函数式编程,因为可以实现数学上的 f(f(f(x)))这种感觉

装饰器,就是嵌套函数,传入函数 f ,对 f 进行加工,添加一些功能,再返回函数,依然给 f
这个变量就切换指向了
实际上是丢弃了原来的函数,变成了增强函数,这就是装饰器

偏函数,函数的某些变量被固定了
这样子返回的是一个函数,可以在适合的时候, f( ) 起作用
经常用在 GUI ,比如一个按钮,先固定尺寸,颜色主题,等固定的变量,空出一个标题和信号绑定
然后后面合适的时候,不断地绑定

======
下面来讲闭包:

闭包,形式上和上面两个有点像,很接近,其实很不一样
首先,闭包的语法形式上要素是:
1.嵌套函数
2.返回的是函数
3.内部函数,引用外部函数的变量,包括嵌套函数的 f(x)的直接参数 x

如:
def f(x):
....a=1
....def g( ):
........nonlocal a,x
........a += 1
........x += 1
........print("a:{},x:{}".format(a,x))
....return g

s = f(5)
s()
s()
s()
====
输出:
a:2,x:6
a:3,x:7
a:4,x:8
[Finished in 0.1s]


就比如这个例子,内部嵌套函数, g 引用了 x , a
这就算闭包了。
要想解释清楚这个名字,得从调用说起。

s = f(5) #这步调用,其实是生成了一个函数 g( ) ,变量名 g 付给了 s
只有当 s( ) 的时候,才启用

退一步,我们都知道,函数会产生自己的局部作用域,对 s 来讲,就是 g 函数
g 函数产生了自己的作用域,但是 g 函数调用了 a , x 两个变量, a,x 又在 f 中,可是 f 又调用结束了,该回收了

解释器陷入两难,于是,算是特殊情况
于是解释器,生成了一个特殊作用域

介于 全局作用域和 f 的局部作用域之间的,作用域

当你反复调用,生成多个函数,该作用域中 a , x 是彼此独立的,这个比较特别,要记住
想一个封闭的包裹,夹在 f 的局部作用域和 全局作用域之间
这个从属于外部函数 f 局部作用域的变量 a,x ,被函数“封闭”起来了。
被封闭起来的变量的寿命,与封闭它的函数寿命相等
名字起得很形象,夹在中间,作用域封闭起来,称为闭包( Closure )。

封闭的作用域,外界访问不到
还有就是,他是不会被回收的,据说,寿命和函数一样长。
如果你设置变量为累加
反复调用,就会积累数值

这个作用域和不会被回收的变量,就叫做环境
所谓的记住环境,就是这些变量的数值,可以保持

变量被称作自由变量
我没看出哪里自由,高等数学里自由变量的意思是可以是任意值,表示为 C
这里大概的意思是,解释器管不着,不被回收

总结一下闭包,之后的特点:
作用域内的变量
1.不会被回收
2.相对封闭,不会被访问到,想一个封闭的包内的变量
3.反复调用,可以积累
4.for 循环使用的时候,总是以最后一个为准,因为是临解释的时候,才使用

换个角度看闭包:

def f(x):
....a=1
....def g( ):
........nonlocal a,x
........a += 1
........x += 1
........print("a:{},x:{}".format(a,x))
....return g

闭包实际上就是记录了外层嵌套函数的作用域中的变量
通过这个 f , g 例子,可以建立多个自定义函数

这很容易让人联想到面向对象编程
f 更像是 g 的构造器,
a,x 是一个私有变量
数据和变量捆绑,函数版本的面向对象
闭包意味着数据与函数结合起来了,这和面向对象思想中的“对象”的概念很接近。

于是闭包有了各种用途

1.可以返回加强的函数,作为函数工厂函数,比如时间戳装饰器
2.可以贮藏闭包作用域中的变量,可以做一个独立环境的计数器
3.可以构造一个私有变量( JS )
4.记住环境, JS 中,可以带着 DOM 的 div 信息,跟到最新点击信息

5.既然都是面向对象,闭包可以实现的, OOP 都可以实现,反过来
OOP 可以实现的功能,闭包有时候可以化繁为简。
banricho
2015-12-22 00:52:24 +08:00
@Mark24 感谢整理,用的语言比我精炼多了,也对我启发很大~
banricho
2015-12-22 21:28:34 +08:00
今天重新整理了一下,之前我的表述和逻辑都存在一定错误…
Mark24
2020-06-19 16:48:54 +08:00
突然看到有人收藏。

研究过《 Ruby 原理剖析》,至于 Python,Ruby,JavaScript 都差不多。至少模型是通用的

我简单再自问自答一下:


闭包产生的形式代码 大概是—— 函数 A 内部定义了函数 B,内部函数 B 引用了外部函数 A 的变量,A 函数返回了函数 B 。
一般来说,一个函数执行完毕,就会被回收掉。这里有点区别,函数 A 执行完毕,返回的是 B 。B 还引用者 A 的变量。

这就是闭包了。被 B 引用的变量,由于存在引用关系无法被切断。就像一个小背包一样,永远携带着。永远可以访问。
反过来,也就无法被回收内存。

本质上底层,是函数 B 保存了对外部环境也就是 A 的作用域链的引用,其实是一个指针。Python,Ruby 都是这样实现的。JS 没看过 V8 源码,应该也是一样的。


闭包有什么好处呢?
其实是有好处的 —— 可以极大地简化代码。
如果没有闭包,会如何? 访问变量,必须靠传参。闭包可以自动向外顺着保存的作用域链的指针,向外自动查找变量。无形中大量简化了代码。

JS,Ruby 中大量的使用闭包,可以让代码非常简洁清晰。
这就是闭包,一个聪明的设计,把一个频繁使用的行为,固化到解释器内部,帮你做。
Mark24
2020-06-19 16:52:05 +08:00
5 年前刚入行。哈哈,5 年后。时间过得真快。

当时的理解趋于表象。也没错,但是没有触及本质。

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/245098

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX