10 | 闭包: 理解了原理,它就不反直觉了
10 | 闭包: 理解了原理,它就不反直觉了
讲述:宫文学
时长14:32大小13.30M
闭包的内在矛盾
函数作为一等公民
实现我们自己的闭包机制
体验一下函数式编程
课程小结
一课一思
赞 5
提建议
精选留言(19)
- 刘強2019-10-27有些东西研究的透彻以后,你就会不由自主的成为哲学家了。
作者回复: 我这几年对复杂系统有关的理论很感兴趣,曾经在校友的一次聚会上分享了一个主题,其中的主要意思,就是从科学甚至可以推导出哲学,印证古老智慧。
共 3 条评论13 - 独钓寒江雪2020-01-15闭包的产生(以JavaScript为例): 1. 因为JavaScript是静态作用域的,所以它内部环境中需要的变量在编译时就确定了,运行时不会改变; 2. 因为JavaScript中,函数是一等公民,可以被调用,可以作为参数传递,可以赋值给变量,也可以作为函数返回值,所以它的运行时环境很容易变化; 3. 当函数作为参数返回时,其外层函数中的变量已经从调用栈弹出,但是我们必须让函数可以访问到它需要的变量,因此运行时的环境和定义时的作用域之间就产生矛盾; 4. 所以我们把内部环境中需要的变量,打包交给内层函数(闭包函数),它就可以随时访问这些变量了,就形成了闭包。展开
作者回复: 总结得非常好!Great!
11 - nil2019-10-17记得第一次遇到闭包是在学习python得时候,方式刚觉这个玩意好牛逼。后来随着对其理解的深入,闭包完全带有面向对象的意思,外层函数通过函数参数的形式给内部函数创建运营期变量,这个运行期作用的变量和oop中的成员变量有相似的味道。通过这一讲,对闭包的实现原理有了进一步的理解,原来闭包不反人类,设计还相当巧妙😁
作者回复: 非常好! 遇到看似不正常的东西的时候,其实就是让认知深化的契机。
共 3 条评论11 - 沉淀的梦想2019-09-06闭包如果引用的是外部函数中的局部变量,直接把这个变量从栈中复制一份到FunctionObject里面就可以了,但是如果应用了全局变量的话,感觉必须要引用全局变量本身,这样才能自己的修改体现在全局变量中。老师代码中是如何实现这个的呢?
作者回复: 非常好,你注意到了这个细节。 实际上,我在运行时用到了一个小技巧。首先是按照作用域查找变量,这个时候就会找到那个全局变量。在作用域里找不到的时候,再到FunctionObject中去找。所以,其实运行期里,全局变量存了两份。一份是在顶层的栈桢里,一份在FunctionObject里。只不过后者不起作用罢了。 当存在多层函数嵌套的时候,上面的算法可以根据运行时所在的作用域,访问正确的变量。 这个方法有些偷懒,因为毕竟FunctionObject里冗余了一份,浪费空间了。你也可以找其他机制来实现。只要支持闭包的原理就行! 你可以参考ASTEvaluator.java中的getLValue()方法,里面有注释,说了这个思路。
共 6 条评论8 - 风2019-10-05怎么看起来像: 闭包变量,就是在语义分析时,为闭包函数生成的static变量。
作者回复: 但只对这个闭包有用。再调用一次函数,新生成一个闭包,就会再生成另一个变量。
共 2 条评论5 - Smallfly2019-09-22我今天发现 IntelliJ 全家桶支持 ANTLR 插件,可以集成在编译器里直接查看生成的 AST。
作者回复: 那更方便了!
6 - Tao2020-02-23JavaScript 函数中 this ,如果是一个函数是对象调用比如 obj.foo(),那么foo中这个this就是当前对象obj 如果这个foo当作普通函数调用如: var bar =obj.foo bar() 这个时候this就不是obj这个对象了,非严格模式下this此时是全局对象 window展开
作者回复: 从语言的设计角度讲,对象的方法和函数没有什么区别。 比如,java程序在调用方法时,会在第一个参数中传递对象引用,这就是this。比如一个void foo(int a)函数,实际会接收到两个参数。
3 - sugar2020-04-16既然聊到JavaScript,我还是想问一句 宫老师真的觉得js的this设计不是败笔吗?感觉在es规范迭代的多个版本中,形成了一个相当沉重的历史包袱。严格模式下,语言制定者(或者说当时有权修改语言规范的人)就对this的含义做了修改。就像前面评论的那位所说,this在非严格模式指向window,而如果开启严格模式这里又是报错了...2
- dbo2020-03-16其实,只要函数能作为值传来传去,就一定会产生作用域不匹配的情况,这样的内在矛盾是语言设计时就决定了的。 不理解这句话,为什么函数作为值传来传去会产生作用域不匹配的情况,考试能解释下吗?谢谢。
作者回复: 有一个概念,叫做词法作用域(Lexical Scope),又叫做静态作用域。也就是变量的声明和使用的关系(变量消解),是在编译期就可以确定的,是完全由变量在源代码中的位置决定的。 对于闭包的情况,内部函数使用了外部函数的变量。但这个内部函数在运行时又被传到其他地方去使用。那么声明这个函数时所依赖的变量,在实际运行时,没有办法从上一级的栈帧里去获取,这就是矛盾的地方。
3 - 曾经瘦过2019-09-25个人理解: JavaScript是 静态作用域 但是JavaScript中this 是动态作用域
作者回复: 应该说,this本来就用来指代当前作用域的。对象就是一个作用域。所以this总在变是应该的。this不需要我们在代码里去声明,它是一个内在的机制。 动态作用域,是指我们在代码里显式声明的变量,其值不是声明时的作用域里的值,而是运行环境的作用域里的值。
共 3 条评论2 - 沉淀的梦想2019-09-07/* 这是针对函数可能是一等公民的情况。这个时候,函数运行时的作用域,与声明时的作用域会不一致。 我在这里设计了一个“receiver”的机制,意思是这个函数是被哪个变量接收了。要按照这个receiver的作用域来判断。 */ else if (frame.object instanceof FunctionObject){ FunctionObject functionObject = (FunctionObject)frame.object; if (functionObject.receiver != null && functionObject.receiver.enclosingScope == f.scope) { frame.parentFrame = f; break; } } 不是很理解老师的这个receiver机制,能举个例子吗?展开
作者回复: 看一下closure.play示例程序: int a = 0; function int() fun1(){ int b = 0; //函数内的局部变量 int inner(){ //内部的一个函数 a = a+1; b = b+1; return b; //返回内部的成员 } return inner; //返回一个函数 } function int() fun2 = fun1(); 这时候,fun2是个变量,这个变量就是fun1()中的inner()函数的receiver。这个时候,inner()函数的运行时坐在的作用域是fun2这个变量的。 receiver这个机制是我创造的,不用拘泥于这种实现方式。只要能够实现闭包的原理,就都可以。
共 2 条评论2 - Geek_d0aef12019-09-06想问个没有技术含量的问题,想确认下,antlr 自动生成的代码只有4个,其他都是自己手动写的?
作者回复: 你运行antlr命令的时候,通过带不同的参数,会生成数量不等的java文件。工具生成的头上都带有注释,说明是Antlr生成的。 在playscript-java项目中,应该是有6个。1个lexer,1个parser,2个是支持listener,2个是支持visitor的。 其他是手动写的:-)
2 - Nail2020-07-09我用的是 ts,从 7 开始,发现很难跟上了。举个例子,比如在实现 visitor 时的很多方法都和 java 的不一样,也没有找到对应的文档。想请问有没有缓解的办法
作者回复: TS实现visitor也很简单。一个办法,是让Antlr给你生成一下,然后你可以借鉴一下。Antlr支持TS。
2 - xxx2021-03-06现在代码确实是看不太懂了,就读读思想把。感觉保存变量那块术语其实就是 Capture,然后大部分语言在这里是允许返回的函数修改capture到的变量的,但Java不可以,所以还得用数组绕过。
- VictorLee2020-09-15那个是闭包面试经典题,另外js中的this实现了类似的动态作用域机制,其确定有一个优先级
作者回复: Great!
- Geek_1796812020-06-25老师好,C语言里用函数指针作为参数传递也能实现本节中 LinkedList 中 map 的作用,可以认为它们是类似的东西吗?比如说函数能作为参数传递的底层也是传递函数地址? 另外一个问题是,C 和 C++ 中没有函数嵌套定义的问题,所以是否也就不存在闭包的概念,即不存在在函数中返回另一个函数的操作场景?
作者回复: 没错,C语言中的函数指针是一种数据类型,可以用来作为参数传递或赋值,从而实现函数式编程的一些优点。 在把函数作为参数传递方面,每种语言的实现机制是不一样的。比如,对于Python而言,函数也是一个对象,跟一个整数、一个字符串是一样的。Python的运行时会根据这个对象里的信息,找到一个具体的函数地址(机器码地址)去执行,或者去解释执行一段字节码。我在《编译原理实战课》对上述机制做了剖析。 关于最后一个问题,C和C++的标准规范里是不支持函数的嵌套定义的。但是,由于C语言具有高度的灵活性,它其实可以模拟出闭包的效果来,你可以在网络上搜索一下其实现机制。
- Aaaaaaaaaaayou2020-02-10老师,有个问题问一下,代码中的 LValue 类型是干嘛用的,为什么有些节点的返回值是 Object,有些是 LValue
作者回复: LValue的意思是左值,也就是可以出现在赋值符号左边的值。它得是一个变量的引用(或C语言中变量的地址),这样才能修改变量的值。 与之对应的,是右值。右值可以仅仅是一个值。也就是示例代码中的Object。 左值可以当做右值来用,而右值不能当做左值来用。
1 - Sun Fei2019-09-07精彩。
作者回复: :-)
- 茶底2019-09-05老师什么时候开始讲lex和yacc啊
作者回复: lex和yacc都没计划讲。因为这些工具都差不多。掌握原理后,用哪个应该都没问题。 lex(或flex)比较简单,所以会用Antlr一定也会用lex。 yacc(或bison)是LR算法的,我们讲完LR算法以后,你理解这个工具的原理应该也没啥问题。