完整课程入口:996 全套课程体系 | Lua 学习路径 | 幂尔框架 mirs.cn
Lua 的 GC 判断"对象是否可回收"看的是引用:只要还有 table 拿着一个对象,它就不会被回收。这带来一个常见死结——你想给一堆对象挂附加数据(缓存计算结果、临时标记),用普通 table 存 cache[obj] = result,结果 obj 被别处销毁了,cache 还死死抓着它,内存永远降不下来。
弱表就是解药:把表的 __mode 元字段设为 "k"(key 弱)、"v"(value 弱)或 "kv",对应位置的引用不计入 GC 判定。对象一旦在其他地方都不可达,它在弱表里的条目会被自动清除。
local cache = setmetatable({}, { __mode = "v" }) -- value 弱引用
cache[player] = computeExpensive(player) -- 玩家对象销毁后
-- cache[player] 条目自动消失,无需手动清理
第一,计算缓存。 给对象附加派生数据(战力值、格式化名称):用弱 value 表缓存,对象活着缓存有效,对象没了缓存自动蒸发,零维护成本。注意 key 用对象本身、value 放结果,而不是反过来。
第二,观察者与事件回调池。 事件总线若用强引用存监听者,忘记反注册就泄漏。把监听者表做成 __mode = "v" 的弱值表,销毁的对象自动从监听列表里蒸发,等于免费的自动反注册(前提是监听者没有其他强引用,用匿名闭包时要留全局句柄)。
第三,对象附加属性袋。 想给引擎传进来的 userdata/外部队象"贴标签"又不能改它的结构:用弱 key 表 attach[obj] = {...},生命周期与对象严格同生共死。
弱表的清理时机由 GC 决定:对象不可达后,条目要等下一轮回收才消失,期间访问可能仍拿到旧值——别把弱表当成"销毁通知器",销毁回调请走显式接口。其次,__mode = "k" 时数字 key 不会被弱化;值类型的数字、布尔、短字符串本身不受弱引用影响(它们不可被回收)。最后,弱表遍历中做增删可能拿到"将死"条目,遍历逻辑要对 nil 值保持宽容。
一个经验法则:给"别人的对象"挂数据用弱 key 表,给自己算的结果做缓存用弱 value 表。记住这句,弱表就不会用歪。