很好,这个问题问得非常扎实 👍
闭包是很多并发、回调、框架设计的基础。
我用通俗版 + 技术版 + 应用场景三层解释。
函数在创建时:
这个“带着环境的函数”就是闭包。
func outer() func() {
x := 10
return func() {
fmt.Println(x)
}
}
这里:
x 本来属于 outer所以:
f := outer()
f() // 10
outer 已经结束了
但 x 还活着
这就是闭包。
闭包 =
函数 + 它创建时的词法环境(变量作用域)
当匿名函数引用了外层变量:
func() {
fmt.Println(x)
}
Go 会:
所以 x 不会被销毁。
⚠️ 关键点:
闭包捕获的是“变量”,不是“变量的值”
for i := 0; i < 3; i++ {
go func() {
fmt.Println(i)
}()
}
你可能以为输出:
0 1 2
但实际可能是:
3 3 3
因为:
所有 goroutine 共享同一个 i。
for i := 0; i < 3; i++ {
i := i
go func() {
fmt.Println(i)
}()
}
或:
for i := 0; i < 3; i++ {
go func(i int) {
fmt.Println(i)
}(i)
}
你代码里:
LLMStream(text, ctx, func(chunk string) {
s.sendJson(...)
})
这个匿名函数:
这就是闭包。
它“记住了”当前 session。
defer func() {
log.Println(name)
}()
匿名函数记住了 name。
func Subscribe(name string) func() {
return func() {
delete(m, name)
}
}
返回的函数记住了 name。
这就是闭包的经典应用。
func Adder(base int) func(int) int {
return func(x int) int {
return base + x
}
}
使用:
add10 := Adder(10)
add20 := Adder(20)
fmt.Println(add10(5)) // 15
fmt.Println(add20(5)) // 25
不同的闭包“记住不同的 base”。
func Logger(next Handler) Handler {
return func(ctx Context) {
log.Println("before")
next(ctx)
log.Println("after")
}
}
闭包让 next 被“包起来”。
因为它让你:
在并发系统中几乎无处不在。
闭包就是:
一个函数“记住”它出生时周围的变量。
或者更形象一点:
函数带着它的“过去”一起活着。
你代码里的:
unsub := func() {
delete(b.eventName, name)
}
这个 unsub:
所以可以在未来某个时刻删除对应的事件。
这就是闭包在事件系统中的典型应用。
如果你愿意,我可以再讲一层更深的:
你现在已经开始写“框架级代码”,理解闭包会让你并发能力提升一个档次 🚀