💎一站式轻松地调用各大LLM模型接口,支持GPT4、智谱、星火、月之暗面及文生图 广告
### 闭包 闭包是由函数及其相关引用环境组合而成的实体(即:闭包=函数+引用环境) “官方”的解释是:所谓“闭包”,指的是一个拥有许多变量和绑定了这些变量的环境的表达式(通常是一个函数),因而这些变量也是该表达式的一部分 javascript闭包示例: ~~~ <script> function a(){ var i=0; function b(){ console.log(++i); document.write("<h1>"+i+"</h1>"); } return b; } $(function(){ var c=a(); c(); c(); c(); //a(); //不会有信息输出 document.write("<h1>=============</h1>"); var c2=a(); c2(); c2(); }); </script> ~~~ 这段代码有两个特点: 函数b嵌套在函数a内部 函数a返回函数b 这样在执行完var c=a()后,变量c实际上是指向了函数b(),再执行函数c()后就会显示i的值,第一次为1,第二次为2,第三次为3,以此类推。 其实,这段代码就创建了一个闭包。因为函数a()外的变量c引用了函数a()内的函数b(),就是说: 当函数a()的内部函数b()被函数a()外的一个变量引用的时候,就创建了一个闭包。 在上面的例子中,由于闭包的存在使得函数a()返回后,a中的i始终存在,这样每次执行c(),i都是自加1后的值。 从上面可以看出闭包的作用就是在a()执行完并返回后,闭包使得Javascript的垃圾回收机制GC不会收回a()所占用的资源,因为a()的内部函数b()的执行需要依赖a()中的变量i。 在给定函数被多次调用的过程中,这些私有变量能够保持其持久性。变量的作用域仅限于包含它们的函数,因此无法从其它程序代码部分进行访问。不过,变量的生存期是可以很长,在一次函数调用期间所创建所生成的值在下次函数调用时仍然存在。正因为这一特点,闭包可以用来完成信息隐藏,并进而应用于需要状态表达的某些编程范型中。 下面来想象另一种情况,如果a()返回的不是函数b(),情况就完全不同了。因为a()执行完后,b()没有被返回给a()的外界,只是被a()所引用,而此时a()也只会被b()引 用,因此函数a()和b()互相引用但又不被外界打扰(被外界引用),函数a和b就会被GC回收。所以直接调用a();是页面并没有信息输出。 下面来说闭包的另一要素引用环境。c()跟c2()引用的是不同的环境,在调用i++时修改的不是同一个i,因此两次的输出都是1。函数a()每进入一次,就形成了一个新的环境,对应的闭包中,函数都是同一个函数,环境却是引用不同的环境。这和c()和c()的调用顺序都是无关的。 ### Go的闭包 Go语言是支持闭包的,这里只是简单地讲一下在Go语言中闭包是如何实现的。 下面我来将之前的JavaScript的闭包例子用Go来实现。 ~~~ package main import ( "fmt" ) func a() func() int { i := 0 b := func() int { i++ fmt.Println(i) return i } return b } func main() { c := a() c() c() c() a() //不会输出i } ~~~ 输出结果: ~~~ 1 2 3 ~~~ 可以发现,输出和之前的JavaScript的代码是一致的。具体的原因和上面的也是一样的,这说明Go语言是支持闭包的。 闭包复制的是原对象指针,这就很容易解释延迟引用现象。 ~~~ package main import "fmt" func test() func() { x := 100 fmt.Printf("x (%p) = %d\n", &x, x) return func() { fmt.Printf("x (%p) = %d\n", &x, x) } } func main() { f := test() f() } ~~~ 输出: ~~~ x (0xc42007c008) = 100 x (0xc42007c008) = 100 ~~~ 在汇编层 ,test 实际返回的是 FuncVal 对象,其中包含了匿名函数地址、闭包对象指针。当调 匿名函数时,只需以某个寄存器传递该对象即可。 ~~~ FuncVal { func_address, closure_var_pointer ... } ~~~ 外部引用函数参数局部变量 ~~~ package main import "fmt" // 外部引用函数参数局部变量 func add(base int) func(int) int { return func(i int) int { base += i return base } } func main() { tmp1 := add(10) fmt.Println(tmp1(1), tmp1(2)) // 此时tmp1和tmp2不是一个实体了 tmp2 := add(100) fmt.Println(tmp2(1), tmp2(2)) } ~~~ 返回2个闭包 ~~~ package main import "fmt" // 返回2个函数类型的返回值 func test01(base int) (func(int) int, func(int) int) { // 定义2个函数,并返回 // 相加 add := func(i int) int { base += i return base } // 相减 sub := func(i int) int { base -= i return base } // 返回 return add, sub } func main() { f1, f2 := test01(10) // base一直是没有消 fmt.Println(f1(1), f2(2)) // 此时base是9 fmt.Println(f1(3), f2(4)) } ~~~