- 闭包
当出现一个函数内嵌套另一个函数时,嵌套函数可以访问父函数的数据及资源,称为闭包
。Lua的闭包原则就是一个函数加上该函数所需要访问的所有变量形成闭包函数
。
function multip()
local i = 0
m = function()
i = i + 1
return i * i
end
return m
end
m1 = multip()
m2 = multip()
print(m1())
-->> 1
print(m1())
-->> 4
print(m2())
-->> 1
print(m1())
-->> 9
print(m2())
-->> 4
在这个例子中,如果按照常规的想法,i
在调用multip()
后已经超出了m
函数的作用范围内,是无法在读取该变量的,但对于程序执行来说,i
变量随着m
函数一起被封包返回,因此不断调用m1
时,i
不断累加,其平方值会不断累增。另外当调用多次multip()
创建多个函数变量时,各自的i
变量累加并不互相影响,这说明每次调用multip
函数时,都创建了一个新的闭包函数,每个闭包函数都各自维护了所需的变量i
。和闭包的定义相互验证。
在之前的章节中,利用Lua函数闭包实现了延迟执行的特性。利用延迟执行可以实现很多功能,比如类似JavaScript中的Callback。班级内进行了一次期中考试,在页面上展示了班级内的学生列表信息,当教师点击学生姓名按钮时,将展示学生的考分以及班级排名。这种需求利用闭包将非常容易实现,在这个需求中,页面上的学生列表都是一致的,因此可以设计一个对象,包含了用户名,同时提供一个回调函数,这样姓名+查询函数将形成一个闭包,并且,对于班级内全部学生,其生成的方式是一致的。
scores = {
{ name = "张三", score = 98 },
{ name = "李四", score = 78 },
{ name = "王五", score = 89 },
{ name = "钱七", score = 65 },
{ name = "赵赵", score = 99 }
}
table.sort(scores, function(n1, n2)
return n1.score > n2.score
end)
function rank(name)
local exist = false
for i, v in pairs(scores) do
if name == v.name then
exist = true
print("分数=" .. v.score .. ",排名=" .. i)
break
end
end
if not exist then
print("学生[" .. name .. "]不存在")
end
end
function student(name)
return {
show = function()
print("查询的学生:" .. name)
end,
rank = function()
return rank(name)
end
}
end
s1 = student("张三")
s1.show()
s1.rank()
-->> 查询的学生:张三
-->> 分数=98,排名=2
s2 = student("qq")
s2.show()
s2.rank()
-->> 查询的学生:qq
-->> 学生[qq]不存在
在这个班级学生成绩查询系统中,scores
为学生成绩数据库,是学生姓名和成绩组成的对象集合;student
是生成页面学生列表的基础对象,其show
函数演示了生成对象后,并在页面生成一个学生姓名按钮的展示动作;当教师点击学生姓名按钮时,该按钮将触发其rank
查询动作;rank
中已经将其所需的资源进行了封包。
在本文开头处,介绍了Lua的函数都是存储在变量中,函数名只是指向了函数时,将绝对值函数指向了print
变量,从而使得print
可以执行绝对值的操作,替代了原有的打印函数。但是按这种操作方式,在这个运行时内,Lua将丢失了对打印函数的指向,如果要还原,还需要重启Lua,或者将打印函数保存到其他变量,再重新指向。如果只是在某些场景下需要这样操作,可以利用闭包实现,而在其他场景中pirnt
功能不变。
function print2(val)
p = print
local print = math.abs
p(val .. "绝对值=" .. print(val))
end
print2(-9)
print("-9绝对值=" .. math.abs(-9))
-->> -9绝对值=9
-->> -9绝对值=9
这种特性非常有使用场景,在特定需求下,Lua的这种设计比Java更好。如果要让内置函数完成一些操作,然后再执行原有功能,比如执行文件读取时,先记录一下日志,就可以通过上述的方式,对内置函数进行改写,如下面的伪码示例:
function r()
local open = io.open
io.open = function(参数)
log("读取日志")
open(参数)
end
end
这种特性对系统升级也有挺重要的帮助,以之前学生成绩查看系统为例,当系统运行中,发现有学生使用了该系统,并查看了各个同学的成绩和排名,造成了成绩较差学生的心理负担,现在开发接手,要添加一个权限验证方案,从session
中获取用户身份,如果为老师,则提供该功能,如果是学生则不能查看成绩和排名。
-- 添加新的权限,不修改原有函数,覆盖了rank函数,并提供了Session中身份的校验,原有调用都不变
local srcRank = rank
rank = function(name)
if teacher == "教师" then
return srcRank(name)
else
print("没有权限不能查看学生成绩")
end
end
-- 重新测试,并模拟读取Session的教师身份
s1 = student("张三")
s1.show()
s1.rank()
-->> 查询的学生:张三
-->> 没有权限不能查看学生成绩
teacher = "教师" -->> Session模拟
s2 = student("王五")
s2.show()
s2.rank()
-->> 查询的学生:王五
-->> 分数=89,排名=3