闭包
8.1、闭包函数
此处可以再去看下 函数作用域
闭包并不只是一个go中的概念,在函数式编程语言中应用较为广泛。
首先看一下维基上对闭包的解释:
提示
在计算机科学中,闭包(英语:Closure),又称词法闭包(Lexical Closure)或函数闭包(function closures),是引用了自由变量(外部非全局)的函数。
简单来说就是一个函数定义中引用了函数外定义的变量,并且该函数可以在其定义环境外被执行。这样的一个函数我们称之为闭包函数。
提示
- 闭包就是当函数可以记住并访问所在的词法作用域时,就产生了闭包,即使函数是在当前词法作用域之 外执行。
提示
- 需要注意的是,自由变量不一定是在局部环境中定义的,也有可能是以参数的形式传进局部环境;另外在 Go 中,函数也可以作为参数传递,因此函数也可能是自由变量。
提示
- 闭包中,自由变量的生命周期等同于闭包函数的生命周期,和局部环境的周期无关。
提示
- 闭包在运行时可以有多个实例,不同的引用环境和相同的函数组合可以产生不同的实例。
实现一个计数器函数,不考虑闭包的情况下,最简单的方式就是声明全局变量:
package main
import "fmt"
var i = 0
func counter() {
i++
fmt.Println(i)
}
func main() {
counter()
counter()
counter()
}
这种方法的一个缺点是全局变量容易被修改,安全性较差。闭包可以解决这个问题,从而实现数据隔离
。
package main
import "fmt"
func getCounter() func() {
var i = 0
return func() {
i++
fmt.Println(i)
}
}
func main() {
counter := getCounter()
counter()
counter()
counter()
counter2 := getCounter()
counter2()
counter2()
counter2()
}
getCounter完成了对变量i以及counter函数的封装,然后重新赋值给counter变量,counter函数和上面案例的counter函数的区别就是将需要操作的自由变量转化为闭包环境。
8.2、闭包函数应用案例
在go语言中可以使用闭包函数来实现装饰器
案例1:计算函数调用次数
package main
import (
"fmt"
"reflect"
"runtime"
)
// 函数计数器
func getCounter(f func()) func() {
calledNum := 0 // 数据隔离
return func() {
f()
calledNum += 1
// 获取函数名
fn := runtime.FuncForPC(reflect.ValueOf(f).Pointer()).Name()
fmt.Printf("函数%s第%d次被调用\n", fn, calledNum)
}
}
// 测试的调用函数
func foo() {
fmt.Println("foo function执行")
}
func bar() {
fmt.Println("bar function执行")
}
func main() {
/*fooAndCounter := getCounter(foo) // 针对foo的计数器
fooAndCounter()
fooAndCounter()
fooAndCounter()
barAndCounter := getCounter(bar)
barAndCounter()
barAndCounter()
barAndCounter()*/
foo := getCounter(foo) // 开放原则
foo()
foo()
foo()
bar := getCounter(bar)
bar()
bar()
}
案例2:计算函数运行时间
package main
import (
"fmt"
"time"
)
func GetTimer(f func(t time.Duration)) func(duration time.Duration) {
return func(t time.Duration) {
t1 := time.Now().Unix()
f(t)
t2 := time.Now().Unix()
fmt.Println("运行时间:", t2-t1)
}
}
func foo(t time.Duration) {
fmt.Println("foo功能开始")
time.Sleep(time.Second * t)
fmt.Println("foo功能结束")
}
func bar(t time.Duration) {
fmt.Println("bar功能开始")
time.Sleep(time.Second * t)
fmt.Println("bar功能结束")
}
func main() {
var foo = GetTimer(foo)
foo(3)
var bar = GetTimer(bar)
bar(2)
}
关键点:将一个功能函数作为自由变量与一个装饰函数封装成一个整体
作为返回值,赋值给一个新的函数变量,这个新的函数变量在调用的时候,既可以完成原本的功能函数又可以实现装饰功能。