自己动手实现Lua(六)Lua闭包
1.前言
一等函数:函数用起来和其他类型的值(比如数字或者字符串)没什么分别,比如说可以把函数存储在数据结构里、赋值给变量、作为参数传递给其他函数或者作为返回值从其他函数里返回等。
比如Lua的写法:
高阶函数:如果一个函数以其他函数为参数,或者返回其他函数我们称这个函数为高阶函数。反之,我们称这个函数为一阶函数。
在许多支持一等函数的语言里,函数实际上都是匿名的,在这些语言里,函数名就是普通的变量名,只是变量的值恰好是函数而已。
作用域:分为静态作用域和动态作用域。
动态作用域:暂不讨论
静态作用域:在编译时就可以确定非局部变量名绑定的变量。
2.闭包
所谓闭包,就是按静态作用域捕获了非局部变量的嵌套函数,现在大家知道为什么在Lua内部函数被称为闭包了吧,因为Lua函数本质上全都是闭包,就算是编译器为我们生成的主函数也不例外,它从外部捕获了_ENV这个特殊的Upvalue。
举个例子:
我们已经知道,Lua编译器会把脚本包装进一个主函数,因此上面的脚本在编译时大致会变成如下样子:
函数 f 捕获了主函数里的两个局部变量,因此我们可以说 f 有两个闭包(upvalue),分别是u和v。Lua编译器会把闭包相关信息编译进函数原型,存放在Upvalue表中,如果我们用luac命令观察上面这段脚本,可以看到函数 f 原型里确实有两个Upvalue。
函数原型Upvalue表的每一项都有4列:第一列是序号,从0开始递增;第二列给出upvalue的名字,第三列指出upvalue捕获的是否是直接外围函数的局部变量,如果是,第四列给出局部变量在外围函数调用帧里的索引。
当闭包捕获的是非直接外围函数的局部变量(发生了函数嵌套)会发生什么呢:
在上面这段脚本里,函数f没有访问任何非局部变量,但是函数g访问了主函数里定义的局部变量u和v。
f 的反编译:
可见,虽然函数f并没有直接访问主函数中的局部变量,但是为了能够让函数g捕获u和v这两个Upvalue,函数f也必须捕获它们。
g 的反编译:
可以看到,函数原型upvalue表的第三列都变成了0,这说明这些upvalue捕获的并非是直接外围函数中的局部变量,而是更外围的函数的局部变量,在这种情况下,upvalue已经由外围函数 f 捕获,嵌套函数直接使用即可。所以第四列表示的是外围函数的upvalue表索引(第一列)。
lua闭包是直接在函数原型中建一个数组,存闭包(被捕获的变量)
如何保证捕获的闭包不被自动垃圾回收掉:lua在栈中建了一个容器用来存所有的闭包(让所有闭包都有引用,函数原型中的闭包数组存的是这个容器的key)。