通常,JS 面试,闭包应该是必考的题目。随着越来越多的语言对函数式范式的支持,闭包问题经常出现。在 Go 语言中也是如此。
这是 Go 语言爱好者周刊第 90 期的一道题目。以下代码输出什么?
package main
import "fmt"
func app() func(string) string {
t := "Hi"
c := func(b string) string {
t = t + " " + b
return t
}
return c
}
func main() {
a := app()
b := app()
a("go")
fmt.Println(b("All"))
fmt.Println(a("All"))
}
闭包 fmt.Println(b(“All”)) 输出 A (Hi All) fmt.Println(a(“All”)) 输出 B (Hi go All)
package main
import "fmt"
func app() func(string) string {
// 1. 每次调用 app() 时,都会创建一个新的、独立的局部变量 't'。
// 它的初始值是 "Hi"。
t := "Hi"
// 2. 定义一个匿名函数(闭包 'c')。
// 这个闭包 'c' "捕获"其外部作用域中的变量 't'。
// 这意味着 'c' 持有对它创建时所在的 app() 函数作用域中的那个特定 't' 的引用。
c := func(b string) string {
// 3. 当闭包 'c' 被调用时,它会修改它所捕获的那个 't'。
// 这个 't' 是在 'c' 被创建时就确定的 't'。
t = t + " " + b
return t
}
// 4. 返回这个闭包 'c'。
return c
}
func main() {
// 5. 第一次调用 app():
// - 在 app() 内部,创建了一个 't' (我们称之为 t_for_a),初始值为 "Hi"。
// - 创建了一个闭包,这个闭包捕获了 t_for_a。
// - 这个闭包被赋值给变量 'a'。
// - 所以,'a' 现在是一个函数,它内部保留了对 t_for_a 的引用。
a := app() // 此时,'a' 绑定的 't' (t_for_a) 的值是 "Hi"
// 6. 第二次调用 app():
// - 再次在 app() 内部,创建了另一个 *全新的、独立的* 't' (我们称之为 t_for_b),初始值也为 "Hi"。
// - 创建了另一个 *全新的、独立的* 闭包,这个闭包捕获了 t_for_b。
// - 这个闭包被赋值给变量 'b'。
// - 所以,'b' 现在是一个函数,它内部保留了对 t_for_b 的引用。
// - 关键点:t_for_a 和 t_for_b 是两个完全不同的内存位置上的变量。
b := app() // 此时,'b' 绑定的 't' (t_for_b) 的值是 "Hi"。
// 'a' 绑定的 't' (t_for_a) 的值仍然是 "Hi",不受影响。
// 7. 调用闭包 'a',并传入参数 "go":
// - 'a' 内部的 t_for_a 被修改:t_for_a = "Hi" + " " + "go" => t_for_a 变为 "Hi go"。
// - a("go") 的返回值是 "Hi go",但这里没有使用这个返回值。
a("go") // 执行后:t_for_a = "Hi go"。 t_for_b 仍然是 "Hi"。
// 8. 调用闭包 'b',并传入参数 "All":
// - 'b' 内部的 t_for_b 被修改:t_for_b = "Hi" + " " + "All" => t_for_b 变为 "Hi All"。
// - b("All") 的返回值是 "Hi All"。
fmt.Println(b("All")) // 输出: Hi All
// 执行后:t_for_a = "Hi go"。 t_for_b = "Hi All"。
// 9. 再次调用闭包 'a',并传入参数 "All":
// - 'a' 内部的 t_for_a (当前值为 "Hi go") 被修改:t_for_a = "Hi go" + " " + "All" => t_for_a 变为 "Hi go All"。
// - a("All") 的返回值是 "Hi go All"。
fmt.Println(a("All")) // 输出: Hi go All
// 执行后:t_for_a = "Hi go All"。 t_for_b = "Hi All"。
}
/*
输出是什么? 这里考的是go闭包的问题
最终输出:
Hi All
Hi go All
*/