一、lua中的作用域
在Lua语言中声明的变量默认是全局变量,声明局部变量需要使用local关键字,和其他语言相比这有点特殊。
-- 全局变量 a = 10 function func() b = 100 -- 仍然是全局变量 local c = 20 -- func的局部变量 end func() print(a + b) -- 输出 110 print(c) -- 输出 nil
运行结果:
二、什么是闭包
我们知道,函数里的本地变量只能在函数内部访问,函数退出之后,作用域就没用了,它对应的栈桢被弹出,作用域中的所有变量所占用的内存也会被收回。
闭包的功能是让一个函数的变量逃不出去,什么意思?意思是当一个函数被当做成是一个返回值时,这个函数里所有引用的变量都不会丢失,不会因为离开了一个变量离开了作用域而导致创建或者使用这个函数时,其变量值会发生变化。
当一个函数内部嵌套另一个函数定义时,内部的函数可以访问外部的函数的局部变量,这种特征在lua中叫:词法定界
lambda表达式就是经典的闭包方式之一。
简单来说,闭包就是能够读取其他函数内部变量的函数。
三、闭包的实现
要实现闭包,需要解决两个问题:
函数是第一类值(first class value),也就是要能把函数像普通数值一样赋值给变量,可以作为参数传递给其他函数,可以作为函数的返回值。
满足词法作用域(Lexical Scope),要让内层函数能够访问外层函数中的变量,不管外层函数退出与否。
在Lua语言中,函数是严格遵循词法作用域的第一类值。具备了实现闭包的条件,下面通过一个例子说明什么是闭包。
例子中,定义了一个全局变量a,和func函数,并且在func函数内部定义了局部变量b。func函数中内嵌并返回了一个test函数,test返回的是全局变量和func的局部变量,我们来看看程序输出结果:
a = 10 function func() local b = 20; function test() a = a + 10; b = b + 20; -- 返回全局变量a和局部变量b return a, b end return test end -- tmpFunc1 就是一个闭包 tmpFunc1 = func() for i=1, 3 do v1, v2 = tmpFunc1() print(v1, v2) end
运行结果:
分析:
内层函数test作为返回值赋值给tmpFunc1 变量后,外层函数func就结束了,但内层函数仍能访问原来外层函数的变量 b,当然,也能访问全局变量 a。
站在func的角度来看,自己已经退出了,那么自己定义的局部变量b就应该失效了,但是,闭包tmpFunc1仍然可以访问,这就是闭包。
四、Upvalue 上值
内嵌函数可以访问外包函数已经创建的局部变量,而这些局部变量则称为该内嵌函数的upvalue,也就是上值。
对于上面的例子,局部变量b就是内嵌函数test的上值。注意,函数的参数也是局部变量,看下面的例子:
function create_func(n) function test() n = n + 100; return n end return test end func1 = create_func(1000) print(func1()) func2 = create_func(50) print(func2())
运行结果
分析:
当 func1 = create_func(1000) 这行代码执行完,局部变量n的生命周期本该结束,但是,因为n已经成为了内嵌函数test的上值,以某种方式“存活”在闭包func1中。
4.1 Upvalue为闭包提供数据共享
4.1.1 函数创建的闭包
一个函数创建的闭包共享一份upvalue。
function create_func(n) function test1() n = n + 100; print(n) end function test2() print(n) end return test1, test2 end func1,func2 = create_func(1000) func1() func2() print("") func1() func2()
运行结果:
结果分析:
func1 和 func2 这两个闭包的函数原型分别是 create_func 中的内嵌函数test1和test2,而 test1 和 test2 引用的upvalue是同一个,即create_func 的形参n(局部变量)。
执行完create_func调用后,Lua发现这两个闭包的upvalue指向的是当前堆栈上的相同变量时,只生成一个拷贝,然后让这两个闭包共享该拷贝,这样任一个闭包对该upvalue进行修改都会被另一个知悉。
可以看到,每次调用 func1都将upvalue的值增加了100,随后func2将更新后的值打印出来。upvalue的这种语义很有价值,它使得闭包之间可以不依赖全局变量进行通讯,从而使代码的可靠性大大提高。
4.1.2 闭包创建的闭包(多重内嵌)
function create_func(n) function test () function test1() n = n + 100; print(n) end function test2() print(n) end return test1, test2 end return test end func = create_func(1000) func1, func2 = func() func1() func2() print("") func1() func2() print("====================") func3, func4 = func() func3() func4() print("") func3() func4()
运行结果:
结果分析:
在执行完 func = create_func(1000) ,create_func 的局部变量 n 的生命周期就结束了,所以当func1,func2这两个闭包被创建时堆栈上根本找不到变量 n。create_func 函数的局部变量n不仅是 test 的 upvalue,也是test1和test2的upvalue。闭包 func 已经把n保存为upvalue,之后func1、func2如果在当前堆栈上找不到变量n就会自动到它们的外包闭包(这里指的是闭包func)的upvalue引用数组中去找.
func3和func4与func和func2共享同一个上值upvalue。因为func3和func4与func和func2都是同一个闭包 func 创建的,所以它们引用的upvalue (变量n)实际也是同一个变量,而它们的upvalue引用都会指向同一个地方。