Lua 闭包
首先看看书中是怎么描述闭包的
Lua 中的函数是带有词法定界(lexical scoping)的第一类值(first-class values)。
第一类值指:在 Lua 中函数和其他值(数值、字符串)一样,函数可以被存放在变量中,也可以存放在表中,可以作为函数的参数,还可以作为函数的返回值。
词法定界指:被嵌套的函数可以访问他外部函数中的变量。这一特性给 Lua 提供了强大的编程能力。
当一个函数内部嵌套另一个函数定义时,内部的函数体可以访问外部的函数的局部变量,这种特征我们称作词法定界。
简单的说闭包是一个函数加上它可以正确访问的 upvalues。
我看这几段描述看了好几遍,还是没有看懂,现在我不考虑书中说的,说说我的理解:
闭包是一个函数加上它可以正确访问的 upvalues,这个函数一般是一个匿名函数,并且定义在另一个函数内部;这个函数可以访问定义在外部函数内的成员变量,参数,以及全局函数。
lua中的函数是一阶类型值(first-class value),定义函数就象创建普通类型值相同(只不过函数类型值的数据主要是一条条指令而已),所以在函数体中仍然能定义函数。假设函数f2定义在函数f1中,那么就称f2为f1的内嵌(inner)函数,f1为f2的外包(enclosing)函数,外包和内嵌都具有传递性,即f2的内嵌必然是f1的内嵌,而f1的外包也一定是f2的外包。内嵌函数能访问外包函数已创建的所有局部变量,这种特性便是所谓的词法定界(lexical scoping),而这些局部变量则称为该内嵌函数的外部局部变量(external local variable)或upvalue。试看如下代码:
- function f1(n)
- -- 函数参数也是局部变量
- local function f2()
- print(n) -- 引用外包函数的局部变量
- end
- return f2
- end
- g1 = f1(1979)
- g1() -- 打印出1979
- g2 = f1(500)
- g2() -- 打印出500
可为什么g2和g1的函数体相同(都是f1的内嵌函数f2的函数体),但打印值不同?这就涉及到一个相当重要的概念——闭包(closure)。事实上,Lua编译一个函数时,会为他生成一个原型(prototype),其中包含了函数体对应的虚拟机指令、函数用到的常量值(数,文本字符串等等)和一些调试信息。在运行时,每当Lua执行一个形如function...end 这样的表达式时,他就会创建一个新的数据对象,其中包含了相应函数原型的引用、环境(environment,用来查找全局变量的表)的引用及一个由所有upvalue引用组成的数组,而这个数据对象就称为闭包。由此可见,函数是编译期概念,是静态的,而闭包是运行期概念,是动态的。g1和g2的值严格来说不是函数而是闭包,并且是两个不相同的闭包,而每个闭包能保有自己的upvalue值,所以g1和g2打印出的结果当然就不相同了。
使用upvalue非常方便,但他们的语义也非常微妙,需要引起注意。比如将f1函数改成:
- function f1(n)
- local function f2()
- print(n)
- end
- n = n + 10
- return f2
- end
- g1 = f1(1979)
- g1() -- 打印出1989
upvalue还能为闭包之间提供一种数据共享的机制。试看下例:
- function Create(n)
- local function foo1()
- print(n)
- end
- local function foo2()
- n = n + 10
- end
- return foo1,foo2
- end
- f1,f2 = Create(1979)
- f1() -- 打印1979
- f2()
- f1() -- 打印1989
- f2()
- f1() -- 打印1999
闭包在创建之时其upvalue就已不在堆栈上的情况也有可能发生,这是因为内嵌函数能引用更外层外包函数的局部变量:
- function Test(n)
- local function foo()
- local function inner1()
- print(n)
- end
- local function inner2()
- n = n + 10
- end
- return inner1,inner2
- end
- return foo
- end
- t = Test(1979)
- f1,f2 = t()
- f1() -- 打印1979
- f2()
- f1() -- 打印1989
- g1,g2 = t()
- g1() -- 打印1989
- g2()
- g1() -- 打印1999
- f1() -- 打印1999
Lua将函数做为基本类型值并支持词法定界的特性使得语言具有强大的抽象能力。而透彻认识函数、闭包和upvalue将帮助程式员善用这种能力。