闭包(Closure),又称词法闭包(Lexical Closure)或函数闭包(function closures),是引用了自由变量的函数。这个被引用的自由变量将和这个函数一同存在,即使已经离开了创造它的环境也不例外。所以,有另一种说法认为闭包是由函数和与其相关的引用环境组合而成的实体。
——Google搜索引擎维基百科
当然,上面那段看上去多少有点拗口了。
说得简单点,闭包实际上就是一种语法糖机制,而这种语法糖机制可以简化编程,从而可以提高代码的可读性。比如,有时候对外部的局部变量进行访问,没这种语法糖机制将会编写冗余的代码。而这正也是可以把这种闭包机制归结为一种设计模式。
这种设计模式的好处就是可以很轻易的访问一个函数的内部的局部变量,再简单点就是通过函数嵌套的方式,闭包可以很轻易的实现函数内部和外部之间的连接。
我们知道函数是一段可执行代码,编译后就失效了,而这些函数在编译时就确定了,在执行时,不会发生变化,每个函数在内存中只有一份实例。而闭包并且在执行时候根据不同的引用变量和函数组合起来可以发生变化,也就意味着可以产生多个实例。还有一个好处就是函数调用结束时就会自动失效,而闭包的好处就是可以让这些变量始终保持在内存中,不会随着函数的调用而消失。
Go语言中不允许函数嵌套定义,但是可以用匿名函数来实现嵌套。在这里就得知道,在Go语言中,函数也是一种类型,这意味着可以把函数当成一个值来传递和返回。函数既可以作为一种返回类型又可以作为其他函数的参数。所以,这样很容易使用函数类型来实现闭包。
现在演示Go中一个简单的闭包
package main
import (
"fmt"
)
func main() {
var x int = 1
a := func() func() {
var y int = 2
return func() {//返回匿名函数
fmt.Printf("x:%d,y:%d\n", x, y)
}
}()
a()
}
在上面的例子中,虽然y是局部变量,但是只要闭包还在使用,闭包引用的变量y就不会消失,这也印证了上面说的闭包可以让这些变量始终保持在内存中,不会随着函数的调用而消失。
再看下面一个例子:
package main
import (
"fmt"
)
func main() {
x := closure(10)
y := x(1)
fmt.Println(y)
}
func closure(x int) func(i int) int {
//fmt.Printf("x:%p\n", &x)
return func(y int) int {
// fmt.Printf("x:%p\n", &x)
return x + y
}
}
闭包里传递的都是变量的引用而非值的拷贝。在上面的一段代码中,如果我们把注视去掉,可以发现输出的x的地址都是一样的。
如果还不清晰,我们继续看下面的一个例子:
package main
import (
"fmt"
)
func main() {
x := 10
y := closure(x)
y() //这里输出11
y() //这里输出12
}
func closure(x int) func() {
return func() {
x++
fmt.Println("closure x:", x)
}
}
函数虽然结束了,但是变量不会立即被销毁。同时也可以清晰地看出内部函数对外部函数的修改是一种对变量的引用的操作,而非值的拷贝。
到了这里,我们就可以清晰的知道闭包可以在嵌套函数中引用非本身函数的外部局部变量,注意是局部变量而非全局变量。这时候通常嵌套的函数被独立出来当成父函数的一个返回值或者赋值给一个变量,这也就可以看出go语言把函数当成一种类型的好处了。
在使用闭包时,函数中的变量是保存在内存中的,不会随着函数的调用结束而释放,这样就会造成内存消耗过大,所以在使用闭包时不能滥用。
领取专属 10元无门槛券
私享最新 技术干货