关于闭包一大堆问题 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
Sign Up Now
For Existing Member  Sign In
Mark24

关于闭包一大堆问题

  •  
  •   Mark24 Dec 21, 2015 2374 views
    This topic created in 3780 days ago, the information mentioned may be changed or developed.

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

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

    想吐槽

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

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

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

    对 JS 各种黑科技深深的不服,设计的漏洞百出。 我倾向于,这是一个漏洞,历史,或者被误用而夸大的特性 而不是经过深深的思考,释放出来的特性 
    11 replies    2020-06-19 16:52:05 +08:00
    SpicyCat
        1
    SpicyCat  
       Dec 21, 2015
    建议看看这个了解下 closure 。
    https://developer.mozilla.org/en-US/docs/Web/Javascript/Closures

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

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

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

    呵呵。
    yangtze
        4
    yangtze  
       Dec 21, 2015 via iPhone
    闭包还是很好用的,产生原因是 ES5 之前 Javascript 仅支持全局作用域和函数作用域,而没有块级作用域

    不过 ES6 中对全局作用域这块添加了另一种解决方案,把 var 改成 let , function 改成 class ,就有块级作用域了,基本就不要专门搞闭包了
    ChefIsAwesome
        5
    ChefIsAwesome  
       Dec 21, 2015 via Android
    知道多少语言有闭包这个概念吗
    banricho
        6
    banricho  
       Dec 21, 2015
    @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
        7
    Mark24  
    OP
       Dec 21, 2015
    @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
        8
    banricho  
       Dec 22, 2015
    @Mark24 感谢整理,用的语言比我精炼多了,也对我启发很大~
    banricho
        9
    banricho  
       Dec 22, 2015
    今天重新整理了一下,之前我的表述和逻辑都存在一定错误…
    Mark24
        10
    Mark24  
    OP
       Jun 19, 2020
    突然看到有人收藏。

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

    我简单再自问自答一下:


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

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

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


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

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

    当时的理解趋于表象。也没错,但是没有触及本质。
    About     Help     Advertise     Blog     API     FAQ     Solana     3237 Online   Highest 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 41ms UTC 13:57 PVG 21:57 LAX 06:57 JFK 09:57
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86