Go中的闭包 | 青训营笔记

96 阅读3分钟

1.什么是闭包

维基百科上关于闭包的解释为:『在计算机科学中,闭包(Closure)是词法闭包(Lexical Closure) 的简称,是引用了自由变量的函数。这个被引用的自由变量将和这个函数一同存在,即使已经离开了创造它的环境也不例外。所以,有另一种说法认为闭包是由函数和与其相关的引用环境组合而成的实体』。在很多场合,人们简单地称闭包是 "带数据的行为"

它典型的一种形式为:
一个外层函数中,有内层函数,
内层函数中,会操作外层函数的局部变量,
外层函数的返回值就是这个内层函数,
有以上特征,就形成了闭包结构。
闭包结构中的外层函数的局部变量并不会随着外层函数的结束而销毁,因为内层函数还在继续使用。

还有一种说法是:一个持有外部环境变量的函数就是闭包

2.概念引入

计算机科学家研究作用域时,有词法作用域和动态作用域。动态作用域中,变量的作用范围取决于函数的调用链,它使得“环境的保存机制”研制十分困难。词法作用域则不然:变量的作用范围取决于它在源代码中定义的位置,在词法解析阶段即可确定。它还被称为静态作用域(static scoping),现在主流语言使用的都是词法作用域。

按照词法作用域,外包函数的参数或局部变量的作用域能够覆盖其内部的嵌套函数,因此嵌套函数使用外包函数的局部变量是最自然的要求

3.闭包的本质

闭包源自于函数能当作值来赋值、来传递参数、来返回值。

一个函数被当作值返回时,也就相当于返回了一个通道,这个通道可以访问这个函数词法作用域中的变量,即函数所需要的数据结构保存了下来,数据结构中的值在外层函数执行时创建,外层函数执行完毕时理因销毁,但由于内部函数作为值返回出去,这些值得以保存下来。而且无法直接访问,必须通过返回的函数。这也就是私有性。

4.闭包的作用

  • 使用函数内部的变量在函数执行完后, 仍然存活在内存中(延长了局部变量的生命周期)
  • 让函数外部可以操作(读写)到函数内部的数据(变量/函数),外部不能修改内部变量的值

5.使用闭包的注意点

  • 函数执行完后, 函数内的局部变量没有释放, 占用内存时间会变长
  • 容易造成内存泄露

6.demo

package main  
  
import "fmt"  

func main() {  
    r1 := increment() //r1推导定义为函数,函数类型和内容与内层函数一致  

    v1 := r1() //调用r1返回值赋值给v1  
    fmt.Println(v1)  
    v2 := r1() //再次调用r1并将返回值赋值给v2  
    fmt.Println(v2)  
    _ = r1()  
    _ = r1()  
    _ = r1()  
    _ = r1()  
    fmt.Println(r1())  

    r2 := increment()  

    fmt.Println(r2())  
    fmt.Println(r1()) //此时r1()的i仍存在  
}  
  
// 自增函数  
func increment() func() int {  
    //外层函数的局部变量i  
    i := 0  
  
    //定义匿名函数,让变量自增并赋值  
    fun := func() int {  
        i++ //此时并没有执行自增,现在相当于是正在定义一个自定义类型,而不是执行函数  
        return i  
    }  
  
    return fun  
}