文章摘要
闭包是Lua中匿名函数"记住"外部变量的机制。当函数引用外层变量时,Lua会自动创建一个闭包对象,其中包含这些变量的引用(称为Upvalue)。这使得函数即使离开原始作用域,仍能访问这些变量。
典型应用场景:
动态生成回调函数(如事件处理器)
创建状态保留的函数(如计数器)
实现简单的面向对象模式
异步编程中保存上下文
注意事项:
循环中创建闭包需小心变量共享问题
避免闭包引用大型对象导致内存泄漏
多个闭包共享同一Upvalue时会相互影响
及时释放不再使用的闭包减轻GC负担
闭包大幅提升了Lua的表达能力,合理使用可使代码更简洁灵活。
第一章:开场白——你第一次听说闭包,心里是不是一团浆糊?
学编程,总会撞上“闭包”这俩字。
新手一脸懵:“闭包到底是个啥?”
更别说“匿名函数闭包”还有什么“upvalue”了。
其实,不光是刚入门的朋友,很多做了几年Lua开发的老哥,都不敢说完全搞明白闭包到底原理、性能和应用,到底是啥、为啥用、用了它到底方便在哪?Upvalue又是啥?
这篇文章就用最通俗的语言、最接近生活的例子,把Lua匿名函数的闭包和upvalue彻底掰开揉碎,从底层原理到实战应用,为你讲透这回事——让你不再害怕踩坑、不再把“闭包”当高大上的玄学,真正掌握闭包,写出更稳健、更灵活的Lua代码!
第二章:什么是“匿名函数”?大白话先说“匿名”
在Lua里,匿名函数很常见。
什么叫匿名函数?就是没有名字的函数。就像逛菜市场的“无名摊”,
你现场现写,不登记名字,临时用一下。
比如:
local foo = function(a, b)
return a + b
end
这里的function(a, b) ... end没有名字,直接赋值给变量foo,这就是匿名函数。
还有一种用法,直接把匿名函数当做参数或回调:
table.sort(mylist, function(a, b)
return a < b
end)
你根本没提前声明什么sortFun,直接把function扔进去用,这就是匿名函数的精髓。
第三章:闭包又是啥?怎么跟匿名函数凑一起了?
闭包,是所有新手绕不过去的关卡。光听名字就很玄乎:闭包、包裹、Closure…
到底是“包什么”、“怎么闭”的?
其实,闭包根本不神秘!
大白话解释:闭包就是,函数里引用了外面定义的变量,这个引用关系打包在函数里,等你以后随时用,那个外部变量的值还能被“记住”!
通俗点,相当于函数带了一个“小背包”,这个背包里装着函数体外部的一些变量(叫“upvalue”),随时带走、用用不丢。
- “闭”是因为你的函数把外部变量的引用关系给封闭保存起来;
- “包”是因为它像背包一样,把外部环境的一些值“打包”带走。
第四章:Upvalue是啥?怎么理解它和闭包的关系?
Upvalue 其实就是Closure(闭包)包含的那些“外层变量”。
Lua的术语叫upvalue:“上层变量”的意思。
举个例子,大白话说明:
function makePow(n)
return function(x)
return x ^ n -- 这里的n就是upvalue
end
end
local pow2 = makePow(2)
print(pow2(5)) -- 输出25,因为它记住了 n=2
在function(x)...里面用到了外层的n,这个n没在里面声明,是外部的、被引用的。
Lua会自动给这个匿名函数打包一个函数对象,这个对象的“背包”里就保存了 n 的值。
以后你怎么用这个函数,它都带着这个 n,不丢!这就是 upvalue。
第五章:再拿现实世界打个比方,让你彻底想明白
现实版的“闭包”是啥感觉?
假如你上学时有个好同桌,他一直借你书看,你毕业后,这哥们把你喜欢看的那本书也带走了,无论他以后去哪,都看你的书。这本书就是“闭包”里带走的upvalue!
或者你去饭店,厨师用的是他家祖传的酱料,哪怕换了店名,他炒菜的味道还是那“背包”里的酱料。这酱料就是upvalue。
而厨师本人,就是匿名函数本身!
第六章:Lua闭包究竟是怎么实现的?源码和底层原理大白话扯一扯
讲点原理,别怕复杂,慢慢听:
- 当你定义匿名函数,而且这个函数用到了函数体外的变量(如上例的 n),Lua会为这个匿名函数分配一个 Closure(闭包)对象;
- 在 Closure 对象里,有一个“upvalue链表”,存储了所有用到的“外部变量”的引用;
- 匿名函数每次执行用到 upvalue,不是抓一份快照,而是“引用”原来变量的内存。这就能做到“跟着变量走”;
- 如果你创建多个闭包,引用同一个外部变量,那这几个闭包其实是共享这个变量,谁改都会变。
举例:
function makeAccum()
local sum = 0
return function(x)
sum = sum + x -- sum是upvalue
return sum
end
end
local f = makeAccum()
print(f(10)) -- 输出10
print(f(20)) -- 输出30
这里的sum跟随着f每次调用动态变,Lua帮你把sum的引用打包进f的闭包里面。只要f还活着,sum也“活着”。
底层,Lua给每个函数一个upvalue数组(闭包链表),每个upvalue都是指向外部局部变量的引用(不是拷贝!),GC垃圾回收时也一起处理。
第七章:为什么闭包通常配合匿名函数一起用?
因为匿名函数本身就是没有名字、临时用的“一手货”,
而闭包这种“带着外部变量跑”的特性,最适合动态生成各种不同逻辑。
比如,你需要动态创建一批回调、定制每一个的外部变量参数,就必须用闭包+匿名函数:
local handlers = {}
for i = 1, 5 do
handlers[i] = function()
print("我是第"..i.."个处理器")
end
end
handlers[3]() -- 输出 "我是第5个处理器" (!!)
注意:闭包在for循环里要小心,有的变量是“共享引用”,不是拷贝!
更稳妥的方法:
for i = 1, 5 do
local index = i -- 创建一个新的局部变量
handlers[i] = function()
print("我是第"..index.."个处理器")
end
end
这样,闭包里的upvalue就是每次的index(不会被后续循环覆盖),否则会踩很多坑!
第八章:闭包的常见应用场景有哪些?
- 回调函数的动态绑定
- 比如网络、事件、UI响应等,每个事件都能带自己的参数
- 工厂函数和生成器
- 如计数器、递增器,每次生成一个不同的逻辑块
- 状态机实现
- 每个状态持有独立的环境变量
- 异步并发处理
- 匿名闭包能帮我们保存调用时的上下文数据
- 表单映射、动态策略
- 每个表单或策略都可分配自己的参数/状态
闭包让Lua的函数变成“带状态的自动机”,极大丰富了代码表达能力。
第九章:闭包和upvalue性能消耗到底有多少?要不要担心?
大白话说,闭包和upvalue的性能消耗主要在“创建”和“管理”阶段:
- 每创建一个闭包,Lua都要在内存新建closure对象,维护一组upvalue绑定
- 如果闭包数量不太大(上千个以内),性能影响很小
- 大量创建闭包,或长期持有上万个closure(比如无限循环动态创建匿名闭包),内存压力会上升,GC回收也重
- upvalue的管理是Lua的强项,但要注意别在循环里疯狂造闭包,尤其引用大对象时
实际开发常见性能问题是:
- 闭包内引用了大表或大数据
- 大量闭包导致GC频繁回收(卡顿)
优化建议:
- 循环里能提前造闭包的提前造
- 大对象不要进闭包
- 上下文传递用轻量对象
第十章:闭包的变量生命周期,大白话彻底捋清楚
很多新手被闭包和变量生命周期绕晕了。再通俗点:
闭包里的upvalue,只要闭包活着,外部变量一直活着。
哪怕你的原始作用域已经结束了,比如函数return了,以后你拿到的闭包变量还是有值。
比如:
function foo()
local x = 100
return function() return x end
end
local f = foo()
print(f()) -- 输出100
虽然foo已经执行完了,但通过闭包f,你还是能拿到x。这就是闭包的精髓!
如果没有闭包,这个x早就被GC吃掉了。
第十一章:闭包和共享upvalue的疑难问题
如果你有多个闭包而upvalue是同一个引用,会不会彼此影响?
答案是:会!
function foo()
local cnt = 0
local function inc()
cnt = cnt + 1
print(cnt)
end
local function dec()
cnt = cnt - 1
print(cnt)
end
return inc, dec
end
local inc, dec = foo()
inc() -- 1
dec() -- 0
inc() -- 1
这里的cnt是inc和dec两个闭包共同的upvalue,谁改都能影响。
若不想影响,得创造多个变量。
第十二章:闭包和垃圾回收,资源管理需要注意点啥?
Lua很智能,闭包和upvalue的生命周期是“只要引用还在,变量不GC”。
但实际项目里,闭包和upvalue可能造成内存泄漏,比如闭包里引用了大对象、文件描述符、网络连接等,闭包没释放,里面的资源也不会释放。
工程建议:
- 及时释放不用的闭包(赋nil让GC吃掉)
- 闭包内不要引用不必要的大东西
- 用weak table(弱引用表)管理闭包和upvalue
- 注意闭包和对象聚合,能解耦就解耦
第十三章:闭包与面向对象的关系,大白话点破
很多Lua项目用闭包来模拟类、对象、成员变量,其实就是闭包+upvalue的组合。
比如工厂函数:
function createAccount(initMoney)
local balance = initMoney
return {
deposit = function(amount)
balance = balance + amount
return balance
end,
withdraw = function(amount)
balance = balance - amount
return balance
end
}
end
local acc = createAccount(100)
print(acc.deposit(50)) -- 150
print(acc.withdraw(30)) -- 120
这里每个方法都是闭包,balance就是所有方法共享的upvalue,实现了类似面向对象写法。这是Lua闭包最常见的用法!
第十四章:实际案例分析——闭包的魔力和陷阱
案例1:做计数器
function counter()
local c = 0
return function()
c = c + 1
return c
end
end
local cnt = counter()
print(cnt()) -- 1
print(cnt()) -- 2
print(cnt()) -- 3
闭包自动帮你记住、递增c。
案例2:for循环闭包坑爹问题
local funs = {}
for i = 1, 5 do
funs[i] = function()
print(i)
end
end
funs[1]() -- 6
funs[2]() -- 6
因为i是upvalue,for循环最后变成6,所有闭包都是6。
修正法(局部变量):
for i = 1, 5 do
local idx = i
funs[i] = function()
print(idx)
end
end
第十五章:闭包和upvalue的应用妙招
- 实现私有变量
- 模拟类和对象成员
- 延长变量作用域
- 实现回调、异步任务
- 工厂批量生成不同功能
- 事件系统、任务队列
- 作为表方法,实现自带上下文的函数
第十六章:闭包性能优化实用法则
- 不要在循环里无限造闭包
- 能复用的闭包提前造
- 闭包里少引用大表、文件等重对象
- 用表缓存函数,减少闭包创建压力
- 用命名函数静态化无闭包需求的函数
- 用profile工具监控内存
第十七章:匿名函数闭包和upvalue常见误区FAQ
Q1: 匿名函数闭包是不是只能用匿名函数做?
A: 不是!任何函数,只要用到外部变量就是闭包,无论名字是否匿名。
Q2: 匿名闭包比命名闭包性能差吗?
A: 没本质差异,主要看创建频率、upvalue数量。
Q3: 闭包是不是很耗内存?
A: 少量没影响,大量闭包要注意内存消耗。
Q4: upvalue是变量快照吗?
A: 不是,是引用关系,随时跟变量内容变化。
Q5: 闭包会造成内存泄漏吗?
A: 有可能,尤其闭包引用外部大对象且没释放。
第十八章:闭包与upvalue的代码实战集锦
闭包做计数器
function counter()
local c = 0
return function()
c = c + 1
return c
end
end
闭包做工厂方法,对象封装
function createPlayer(name)
local hp = 100
return {
getName = function() return name end,
getHP = function() return hp end,
hurt = function(d) hp = hp - d end
}
end
local player = createPlayer("老王")
player.hurt(10)
print(player.getHP()) -- 90
第十九章:闭包与Upvalue的更多进阶话题
1. 多闭包共享同一个upvalue
function manyFun()
local val = 0
local f1 = function() print(val) end
local f2 = function() val = val + 1 end
return f1, f2
end
local ff, gg = manyFun()
gg()
ff() -- 打印1
2. 用闭包实现延时任务
function delayPrint(msg, sec)
return function()
os.execute("sleep " .. sec)
print(msg)
end
end
local dp = delayPrint("Hello!", 2)
dp() -- 两秒后打印
第二十章:闭包和upvalue带来的编程哲学与优势
你想象不到闭包到底提高了多少编程自由:
- 函数能带私有变量
- 动态自动机状态不怕丢
- 回调灵活封装,模块高度可扩展
- 状态隔离,让异步和高并发不出bug
- “类”功能一把梭,面向对象白嫖到位
闭包和upvalue是Lua高级编程的杀手锏。懂了它,写代码就像用多功能瑞士军刀,啥需求都能灵活满足!
总结句:闭包就是函数的背包,upvalue就是背包里的装备
回头来看,闭包一点都不神秘:
- 匿名函数用了外部变量,Lua自动帮你挂了一个背包
- 背包里是upvalue(外部变量引用),用的时候随时拿
- 闭包让函数能带着状态跑,能私有变量、能做工厂、回调、面向对象,都变得随心所欲!
记住:闭包(Closure)=函数 + upvalue(引用外部变量链)
掌握了它,Lua代码再也不绕、再也不怕复杂逻辑!
到底是啥?一篇大白话讲到骨子里&spm=1001.2101.3001.5002&articleId=154802428&d=1&t=3&u=682ed62a2721450a803fb9f16e71ad42)
3487

被折叠的 条评论
为什么被折叠?



