跳到主要内容

闭包

8.1、闭包函数

此处可以再去看下 函数作用域

闭包并不只是一个go中的概念,在函数式编程语言中应用较为广泛。

首先看一下维基上对闭包的解释:

提示

在计算机科学中,闭包(英语:Closure),又称词法闭包(Lexical Closure)或函数闭包(function closures),是引用了自由变量(外部非全局)的函数。

简单来说就是一个函数定义中引用了函数外定义的变量,并且该函数可以在其定义环境外被执行。这样的一个函数我们称之为闭包函数。

提示
  1. 闭包就是当函数可以记住并访问所在的词法作用域时,就产生了闭包,即使函数是在当前词法作用域之 外执行。
提示
  1. 需要注意的是,自由变量不一定是在局部环境中定义的,也有可能是以参数的形式传进局部环境;另外在 Go 中,函数也可以作为参数传递,因此函数也可能是自由变量。
提示
  1. 闭包中,自由变量的生命周期等同于闭包函数的生命周期,和局部环境的周期无关。
提示
  1. 闭包在运行时可以有多个实例,不同的引用环境和相同的函数组合可以产生不同的实例。

实现一个计数器函数,不考虑闭包的情况下,最简单的方式就是声明全局变量:

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)
}

关键点:将一个功能函数作为自由变量与一个装饰函数封装成一个整体作为返回值,赋值给一个新的函数变量,这个新的函数变量在调用的时候,既可以完成原本的功能函数又可以实现装饰功能。