闭包和upvalue
lua构建一个新的函数时, 会创建一个函数对象, 其中包含了函数本身的原型还有函数使用到的外部变量或者常量. 当这个新的函数处于某个函数内部, 我们将这个对象称之为闭包, 它所引用的外部函数的局部变量叫做upvalue.
下面是一个例子:
function f1(n)
local upvalue = "hello"
local f2 = function()
print(upvalue .. " " .. n)
end
return f2
end
g1 = f1(500)
g1()
print(g1)
g2 = f1(1500)
g2()
print(g2)
$ luajit copy.lua
$ hello 500
$ function: 0x00921ae0
$ hello 1500
$ function: 0x00926f28
每次调用f1将生成一个以f2为函数原型, 加上两个upvalue值(n, upvalue)的新函数(闭包). 每个新的闭包保持同一函数原型, 分别保存不同的upvalue. 严格来说, 闭包属于动态的概念, 是在程序运行阶段确定的.
upvalue值的作用域
upvalue实际上是闭包的外部函数的局部变量, 存在于外部函数的堆栈中, 在外部函数的执行过程中, 闭包通过访问外部函数堆栈的upvalue引用来访问upvalue, 一旦外部函数的调用过程完毕, 函数的堆栈被清空, upvalue也会被清除, 此时闭包会将upvalue复制到自己的函数堆栈中以便使用. 外部函数(也可以是闭包)执行完毕之前, 内部的闭包和其子闭包使用的都是外部函数堆栈中的那一份, 任何修改都会影响所有的函数(外部函数, 闭包和其子闭包), 当外部函数执行完毕后, 内部的闭包和其子闭包使用的是内部闭包从外部函数复制的那一份, 可以以此类推到最里层的闭包. 下面举例说明.
function f1(n)
local upvalue = "hello"
local f0 = function()
local f2 = function()
n = n * 2
print(upvalue .. " f2 " .. n)
end
local f3 = function()
print(upvalue .. " f3 " .. n)
end
print(upvalue .. " f0 " .. n)
return f2, f3
end
g1, g2 = f0()
g1()
g2()
print(upvalue .. " f1 " .. n)
return f0
end
g0 = f1(500)
$ luajit copy.lua
$ hello f0 500
$ hello f2 1000
$ hello f3 1000
$ hello f1 1000
f1是外部函数, n是其内部变量, 也是内部的f2, f3形成的闭包的upvalue. 要注意的是, 每次进入f1时, 变量n都是不同的, 生成的闭包共享和保存的是当次进入外部函数时的局部变量.
闭包的作用
闭包的主要作用有两个, 一是简洁, 不需要在不使用时生成对象, 也不需要函数名, 二是可以捕获外部变量形成不同的调用环境. 从其名称可以看出来, 闭包的最主要作用是后者, 即可以捕获外部变量.
下面举例说明:
function f1()
local i = 0
return function()
i = i + 1
return i
end
end
g1 = f1()
g2 = f1()
print("g1 " .. g1())
print("g1 " .. g1())
print("------------------------")
print("g2 " .. g2())
print("g2 " .. g2())
$ luajit test.lua
> g1 1
> g1 2
> ------------------------
> g2 1
> g2 2
每次调用f1时会生成不同的局部变量i(值为函数定义时的初始值), 此时闭包将i保存和使用, 与另外的闭包独立开来. g1和g2共享一份函数定义和upvalue初始值, 但是调用环境独立.
这种特性最典型的使用场景是迭代器:
function f1(t)
local i = 0
return function()
i = i + 1
return t[i]
end
end
t = {"111", "heell", 5, 6, "3333"}
for e in f1(t) do
print(e)
end
$ luajit test.lua
> 111
> heell
> 5
> 6
> 3333