编译原理之美
宫文学
北京物演科技CEO
立即订阅
8171 人已学习
课程目录
已完结 43 讲
0/4登录后,你可以任选4讲全文学习。
开篇词 (1讲)
开篇词 | 为什么你要学习编译原理?
免费
实现一门脚本语言 · 原理篇 (13讲)
01 | 理解代码:编译器的前端技术
02 | 正则文法和有限自动机:纯手工打造词法分析器
03 | 语法分析(一):纯手工打造公式计算器
04 | 语法分析(二):解决二元表达式中的难点
05 | 语法分析(三):实现一门简单的脚本语言
06 | 编译器前端工具(一):用Antlr生成词法、语法分析器
07 | 编译器前端工具(二):用Antlr重构脚本语言
08 | 作用域和生存期:实现块作用域和函数
09 | 面向对象:实现数据和方法的封装
10 | 闭包: 理解了原理,它就不反直觉了
11 | 语义分析(上):如何建立一个完善的类型系统?
12 | 语义分析(下):如何做上下文相关情况的处理?
13 | 继承和多态:面向对象运行期的动态特性
实现一门脚本语言 · 应用篇 (2讲)
14 | 前端技术应用(一):如何透明地支持数据库分库分表?
15 | 前端技术应用(二):如何设计一个报表工具?
实现一门脚本语言 · 算法篇 (3讲)
16 | NFA和DFA:如何自己实现一个正则表达式工具?
17 | First和Follow集合:用LL算法推演一个实例
18 | 移进和规约:用LR算法推演一个实例
实现一门脚本语言 · 热点答疑与用户故事 (2讲)
19 | 案例总结与热点问题答疑:对于左递归的语法,为什么我的推导不是左递归的?
用户故事 | 因为热爱,所以坚持
编译原理 · 期中考试周 (1讲)
期中考试 | 来赴一场100分的约定吧!
免费
实现一门编译型语言 · 原理篇 (12讲)
20 | 高效运行:编译器的后端技术
21 | 运行时机制:突破现象看本质,透过语法看运行时
22 | 生成汇编代码(一):汇编语言其实不难学
加餐 | 汇编代码编程与栈帧管理
23 | 生成汇编代码(二):把脚本编译成可执行文件
24 | 中间代码:兼容不同的语言和硬件
25 | 后端技术的重用:LLVM不仅仅让你高效
26 | 生成IR:实现静态编译的语言
27 | 代码优化:为什么你的代码比他的更高效?
28 | 数据流分析:你写的程序,它更懂
29 | 目标代码的生成和优化(一):如何适应各种硬件架构?
30 | 目标代码的生成和优化(二):如何适应各种硬件架构?
实现一门编译型语言 · 应用篇 (2讲)
31 | 内存计算:对海量数据做计算,到底可以有多快?
32 | 字节码生成:为什么Spring技术很强大?
实现一门编译型语言 · 扩展篇 (3讲)
33 | 垃圾收集:能否不停下整个世界?
34 | 运行时优化:即时编译的原理和作用
35 | 案例总结与热点问题答疑:后端部分真的比前端部分难吗?
面向未来的编程语言 (3讲)
36 | 当前技术的发展趋势以及其对编译技术的影响
37 | 云编程:云计算会如何改变编程模式?
38 | 元编程:一边写程序,一边写语言
结束语 (1讲)
结束语 | 用程序语言,推动这个世界的演化
编译原理之美
登录|注册

10 | 闭包: 理解了原理,它就不反直觉了

宫文学 2019-09-04
在讲作用域和生存期时,我提到函数里的本地变量只能在函数内部访问,函数退出之后,作用域就没用了,它对应的栈桢被弹出,作用域中的所有变量所占用的内存也会被收回。
但偏偏跑出来闭包(Closure)这个怪物。
在 JavaScript 中,用外层函数返回一个内层函数之后,这个内层函数能一直访问外层函数中的本地变量。按理说,这个时候外层函数已经退出了,它里面的变量也该作废了。可闭包却非常执着,即使外层函数已经退出,但内层函数仿佛不知道这个事实一样,还继续访问外层函数中声明的变量,并且还真的能够正常访问。
不过,闭包是很有用的,对库的编写者来讲,它能隐藏内部实现细节;对面试者来讲,它几乎是前端面试必问的一个问题,比如如何用闭包特性实现面向对象编程?等等。
本节课,我会带你研究闭包的实现机制,让你深入理解作用域和生存期,更好地使用闭包特性。为此,要解决两个问题:
函数要变成 playscript 的一等公民。也就是要能把函数像普通数值一样赋值给变量,可以作为参数传递给其他函数,可以作为函数的返回值。
要让内层函数一直访问它环境中的变量,不管外层函数退出与否。
我们先通过一个例子,研究一下闭包的特性,看看它另类在哪里。

闭包的内在矛盾

取消
完成
0/1000字
划线
笔记
复制
© 版权归极客邦科技所有,未经许可不得传播售卖。 页面已增加防盗追踪,如有侵权极客邦将依法追究其法律责任。
该试读文章来自付费专栏《编译原理之美》,如需阅读全部文章,
请订阅文章所属专栏。
立即订阅
登录 后留言

精选留言(10)

  • nil
    记得第一次遇到闭包是在学习python得时候,方式刚觉这个玩意好牛逼。后来随着对其理解的深入,闭包完全带有面向对象的意思,外层函数通过函数参数的形式给内部函数创建运营期变量,这个运行期作用的变量和oop中的成员变量有相似的味道。通过这一讲,对闭包的实现原理有了进一步的理解,原来闭包不反人类,设计还相当巧妙😁

    作者回复: 非常好!
    遇到看似不正常的东西的时候,其实就是让认知深化的契机。

    2019-10-17
    3
  • 怎么看起来像:
    闭包变量,就是在语义分析时,为闭包函数生成的static变量。

    作者回复: 但只对这个闭包有用。再调用一次函数,新生成一个闭包,就会再生成另一个变量。

    2019-10-05
    2
  • Smallfly
    我今天发现 IntelliJ 全家桶支持 ANTLR 插件,可以集成在编译器里直接查看生成的 AST。

    作者回复: 那更方便了!

    2019-09-22
    1
  • 沉淀的梦想
    /*
                    这是针对函数可能是一等公民的情况。这个时候,函数运行时的作用域,与声明时的作用域会不一致。
                    我在这里设计了一个“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这个机制是我创造的,不用拘泥于这种实现方式。只要能够实现闭包的原理,就都可以。

    2019-09-07
    1
    1
  • Geek_d0aef1
    想问个没有技术含量的问题,想确认下,antlr 自动生成的代码只有4个,其他都是自己手动写的?

    作者回复: 你运行antlr命令的时候,通过带不同的参数,会生成数量不等的java文件。工具生成的头上都带有注释,说明是Antlr生成的。
    在playscript-java项目中,应该是有6个。1个lexer,1个parser,2个是支持listener,2个是支持visitor的。
    其他是手动写的:-)

    2019-09-06
    1
  • 刘強
    有些东西研究的透彻以后,你就会不由自主的成为哲学家了。

    作者回复: 我这几年对复杂系统有关的理论很感兴趣,曾经在校友的一次聚会上分享了一个主题,其中的主要意思,就是从科学甚至可以推导出哲学,印证古老智慧。

    2019-10-27
  • 曾经瘦过
    个人理解: JavaScript是 静态作用域 但是JavaScript中this 是动态作用域

    作者回复: 应该说,this本来就用来指代当前作用域的。对象就是一个作用域。所以this总在变是应该的。this不需要我们在代码里去声明,它是一个内在的机制。

    动态作用域,是指我们在代码里显式声明的变量,其值不是声明时的作用域里的值,而是运行环境的作用域里的值。

    2019-09-25
    1
  • SUNFEI
    精彩。

    作者回复: :-)

    2019-09-07
  • 沉淀的梦想
    闭包如果引用的是外部函数中的局部变量,直接把这个变量从栈中复制一份到FunctionObject里面就可以了,但是如果应用了全局变量的话,感觉必须要引用全局变量本身,这样才能自己的修改体现在全局变量中。老师代码中是如何实现这个的呢?

    作者回复: 非常好,你注意到了这个细节。
    实际上,我在运行时用到了一个小技巧。首先是按照作用域查找变量,这个时候就会找到那个全局变量。在作用域里找不到的时候,再到FunctionObject中去找。所以,其实运行期里,全局变量存了两份。一份是在顶层的栈桢里,一份在FunctionObject里。只不过后者不起作用罢了。

    当存在多层函数嵌套的时候,上面的算法可以根据运行时所在的作用域,访问正确的变量。

    这个方法有些偷懒,因为毕竟FunctionObject里冗余了一份,浪费空间了。你也可以找其他机制来实现。只要支持闭包的原理就行!

    你可以参考ASTEvaluator.java中的getLValue()方法,里面有注释,说了这个思路。

    2019-09-06
    1
  • 茶底
    老师什么时候开始讲lex和yacc啊

    作者回复: lex和yacc都没计划讲。因为这些工具都差不多。掌握原理后,用哪个应该都没问题。
    lex(或flex)比较简单,所以会用Antlr一定也会用lex。
    yacc(或bison)是LR算法的,我们讲完LR算法以后,你理解这个工具的原理应该也没啥问题。

    2019-09-05
收起评论
10
返回
顶部