完整课程入口:996 全套课程体系 | Lua 学习路径 | 幂尔框架 mirs.cn
在 Lua 里,函数是"第一类值"——可以存进 table、当参数传递、也能被别的函数返回。当一个内部函数引用了外部函数的局部变量时,这对"函数 + 被捕获的变量"就叫闭包,那些被捕获的变量称为 upvalue。
理解 upvalue 的关键在于:它不是值的拷贝,而是变量本身的共享。两个闭包如果捕获了同一个外部局部变量,它们看到的是同一个存储槽,一个改、另一个立刻能读到:
local function makeCounter()
local n = 0
return {
inc = function() n = n + 1; return n end,
get = function() return n end,
}
end
local c = makeCounter()
c.inc(); c.inc()
release_print(c.get()) -- 输出 2,两个闭包共享同一个 n
这个特性让闭包天然适合做"带私有状态的模块":n 被闭包捕获后外部无法直接访问,只能通过返回的接口读写,等于免费的私有成员。
第一,回调携带上下文。 给按钮注册点击事件时,把界面元素、玩家数据直接捕获进闭包,回调触发时无需再查一遍:
GUI:addOnClickEvent(btn, function()
SL:PlayAudio(71100)
release_print("来自面板", panelId)
end)
第二,工厂函数批量生成逻辑。 每个怪物需要一份"受击后延时回血"逻辑,用工厂函数为每只怪生成独立闭包,各自持有自己的血量上限,互不干扰。
第三,装饰器与权限包裹。 写一个 wrap(old, check),返回一个先做 GM 权限校验、再调用原函数的新闭包,可以无侵入地给一批管理函数统一加锁。
热更新的本质是"替换函数",而函数替换能不能保住旧状态,全看 upvalue。直接用 load 编译出一份新函数覆盖全局接口时,新函数的 upvalue 是全新的,旧逻辑里积累的状态会丢。稳妥做法是替换后立刻把关键的旧 upvalue 逐个迁移过来,或者把状态统一寄存在 table 里、函数只作为无状态的纯逻辑被替换——这也是商业热更方案普遍要求"状态与逻辑分离"的原因。
最后一个坑:闭包会延长变量的生命周期。捕获了大对象(整张玩家数据表)的回调如果被注册进全局事件总线又从不反注册,这个大对象就永远不会被回收。凡是用闭包注册的监听,务必在界面销毁时成对移除。