前端开发转 Go 全栈(五):终于遇到熟人了,Go 的闭包和高阶函数原来这么像 JavaScript
这是「前端开发转 Go 全栈」系列的第五篇。
上一篇学习了命名返回值、裸
return和defer,并通过代码实验验证了多个defer的执行顺序、参数求值时机,以及defer对命名返回值的影响。这一篇继续学习 Go 的函数,进入一个前端开发者非常熟悉的领域:匿名函数、闭包与高阶函数。
前言:学了这么久 Go,终于碰到点熟悉的东西了
前面几篇学下来,我对 Go 最大的感受就是:
熟悉的概念不少,但写法和思维方式经常与 JavaScript 不太一样。
比如变量:
go
name := "Starry"
比如错误处理:
go
result, err := doSomething()
if err != nil {
return err
}
再比如上一篇刚学完的 defer:
go
defer file.Close()
这些内容虽然慢慢能够理解,但对于一个长期使用 JavaScript 和 TypeScript 的前端开发者来说,多少还是有点"进入别人主场"的感觉。
直到这一阶段开始学习匿名函数和闭包。
看到下面这种代码时:
go
add := func(a, b int) int {
return a + b
}
我的第一反应终于不是:
这又是什么 Go 特有语法?
而是:
这个我熟啊。
如果换成 JavaScript:
javascript
const add = (a, b) => a + b;
除了语法长得不一样,背后的思想几乎就是同一回事。
继续往下学习闭包、返回函数、函数作为参数,我甚至开始产生一种错觉:
我是不是暂时从 Go 教程切回前端了?
这一篇就来记录一下这段难得"亲切"的学习过程。
函数也可以保存到变量中
前面学习函数时,一直都是这样定义:
go
func add(a, b int) int {
return a + b
}
然后调用:
go
result := add(10, 20)
这当然没有问题。
但 Go 的函数也可以没有名字。
我第一次写了这样的代码:
go
package main
import "fmt"
func main() {
add := func(a, b int) int {
return a + b
}
result := add(10, 20)
fmt.Println("计算结果:", result)
}
运行:
bash
go run .
输出:
text
计算结果: 30
重点在这里:
go
add := func(a, b int) int {
return a + b
}
右边创建了一个没有名字的函数:
go
func(a, b int) int {
return a + b
}
然后将这个函数值保存到变量:
text
add
之后就可以像调用普通函数一样,通过变量调用它:
go
add(10, 20)
这里创建的,就是一个匿名函数。
这不就是 JavaScript 函数表达式吗?
如果换成 JavaScript,可以写成函数表达式:
javascript
const add = function (a, b) {
return a + b;
};
或者使用现在前端开发中更常见的箭头函数:
javascript
const add = (a, b) => {
return a + b;
};
再短一点:
javascript
const add = (a, b) => a + b;
把 Go 和 JavaScript 放在一起对比:
css
javascript
const add = (a, b) => a + b;
go
add := func(a, b int) int {
return a + b
}
虽然 Go 没有箭头函数这种简写,但核心思想是相同的:
text
函数也是一种值
→ 可以创建
→ 可以保存到变量
→ 可以通过变量调用
这一点对于前端开发者来说,几乎没有理解成本。
需要注意的是:
匿名函数只是"没有名字的函数",并不代表所有匿名函数都是闭包。
只有当函数引用了它外层作用域中的变量时,才开始体现闭包的特征。
匿名函数也可以立即执行
既然函数可以直接创建,那么能不能在创建后马上执行?
可以。
go
package main
import "fmt"
func main() {
func() {
fmt.Println("这个匿名函数立即执行了")
}()
}
关键在最后:
go
}()
前面的部分:
go
func() {
fmt.Println("这个匿名函数立即执行了")
}
只是定义了一个匿名函数。
最后的:
go
()
才是真正调用它。
运行结果:
text
这个匿名函数立即执行了
看到这里,前端开发者应该又会觉得很熟悉。
JavaScript 中经常会看到 IIFE,也就是立即调用函数表达式:
javascript
(function () {
console.log("立即执行");
})();
使用箭头函数也可以写成:
javascript
(() => {
console.log("立即执行");
})();
Go 只是换成了:
go
func() {
fmt.Println("立即执行")
}()
整体思路并没有太大区别:
text
定义函数
→ 不保存到变量
→ 立即调用
立即执行函数也可以接收参数
我又试了一下给立即执行的匿名函数传递参数:
go
package main
import "fmt"
func main() {
func(name string) {
fmt.Println("你好:", name)
}("Starry")
}
输出:
text
你好: Starry
这里的函数定义是:
go
func(name string) {
fmt.Println("你好:", name)
}
调用时传入:
go
("Starry")
如果用 JavaScript,可以写成:
javascript
javascript
((name) => {
console.log("你好:", name);
})("Starry");
思路基本一致。
这一阶段开始让我明显感觉到:
Go 和 JavaScript 虽然是两门完全不同的语言,但很多编程思想其实是共通的。
真正需要重新学习的,很多时候只是这门语言如何表达这些思想。
闭包:函数可以访问并修改外层变量
接下来开始学习闭包。
第一段代码:
go
package main
import "fmt"
func main() {
count := 0
increase := func() {
count++
fmt.Println("函数内部 count:", count)
}
increase()
increase()
increase()
fmt.Println("最终 count:", count)
}
运行结果:
text
函数内部 count: 1
函数内部 count: 2
函数内部 count: 3
最终 count: 3
这里有一个关键点:
go
count := 0
是在匿名函数外部声明的。
但是下面的匿名函数不仅可以访问 count,还可以直接修改它:
go
increase := func() {
count++
}
调用三次以后:
text
0
→ 1
→ 2
→ 3
这里的匿名函数引用了外层作用域中的 count,因此形成了闭包。
对前端来说,闭包真的太熟了
换成 JavaScript:
javascript
let count = 0;
const increase = () => {
count++;
console.log(count);
};
increase();
increase();
increase();
同样会得到:
text
1
2
3
所以这一部分几乎可以直接沿用以前对 JavaScript 闭包的理解:
一个函数可以访问它定义时所在词法作用域中的变量。
Go:
go
count := 0
increase := func() {
count++
}
JavaScript:
javascript
let count = 0;
const increase = () => {
count++;
};
这种熟悉感,和前面学习 defer 时完全是两种体验。
不过这里也需要区分两个概念。
匿名函数
go
add := func(a, b int) int {
return a + b
}
这个函数没有名称,所以它是匿名函数。
但是它没有访问任何外部变量,不需要依赖外层状态。
闭包
go
count := 0
increase := func() {
count++
}
这个函数引用并修改了外层的 count,因此形成了闭包。
可以简单理解为:
text
匿名函数关注的是:函数有没有名字
闭包关注的是:函数有没有引用外层作用域中的变量
匿名函数经常被用来创建闭包,但二者不是完全相同的概念。
这样还没有完全体现闭包的价值
前面的 count 直接存在于 main 中:
go
count := 0
如果只是这样,好像还看不出闭包到底有什么特别。
于是,我继续写了一个计数器:
go
package main
import "fmt"
func createCounter() func() int {
count := 0
return func() int {
count++
return count
}
}
func main() {
counter := createCounter()
fmt.Println(counter())
fmt.Println(counter())
fmt.Println(counter())
}
运行结果:
text
1
2
3
这段代码就开始有意思了。
func createCounter() func() int 到底怎么读?
第一次看到下面的函数声明时:
go
func createCounter() func() int
确实有点套娃。
可以将它拆开理解。
前面:
go
func createCounter()
表示:
定义一个名为
createCounter的函数,它不接收参数。
后面:
go
func() int
是 createCounter 的返回值类型。
它表示:
返回一个没有参数、返回值类型为
int的函数。
所以完整的函数声明:
go
func createCounter() func() int
可以读成:
createCounter是一个函数,它会返回另一个"没有参数、返回一个int"的函数。
如果换成 TypeScript,大概是:
typescript
function createCounter(): () => number
这么一对比,一下就顺眼了。
Go:
go
func createCounter() func() int
TypeScript:
typescript
function createCounter(): () => number
本质上都在描述:
一个函数返回另一个函数。
createCounter 结束了,count 为什么还活着?
真正让我觉得闭包有意思的是这里。
createCounter 内部声明了一个局部变量:
go
count := 0
按照之前对局部变量的理解,函数执行结束后:
go
createCounter()
里面的局部变量似乎也应该无法再被访问。
但是,createCounter 返回了下面这个匿名函数:
go
return func() int {
count++
return count
}
这个匿名函数仍然引用着 count。
于是:
go
counter := createCounter()
拿到的并不只是一段"让数字加一"的函数逻辑。
它还保留了对 count 的访问。
第一次调用:
go
counter()
得到:
text
1
第二次调用得到:
text
2
第三次调用得到:
text
3
也就是说,这个函数"记住"了之前的状态。
可以暂时把它想象成:
text
counter
├── 执行逻辑:count++
└── 持续引用的状态:count
这就是闭包非常典型的用途:
将一段函数逻辑和它需要持续访问的状态绑定在一起。
至于 count 最终存储在哪里、编译器具体如何处理变量的生命周期,这一阶段暂时不深入。
现在只需要知道:
只要返回的函数仍然引用
count,后续调用这个函数时,就依然能够读取和修改这份状态。
JavaScript 版本几乎一模一样
Go:
go
func createCounter() func() int {
count := 0
return func() int {
count++
return count
}
}
JavaScript:
javascript
function createCounter() {
let count = 0;
return function () {
count++;
return count;
};
}
或者使用箭头函数:
javascript
const createCounter = () => {
let count = 0;
return () => {
count++;
return count;
};
};
调用:
javascript
const counter = createCounter();
console.log(counter()); // 1
console.log(counter()); // 2
console.log(counter()); // 3
到了这里,对前端开发者来说,Go 闭包已经基本没有陌生感了。
语法虽然不同,但执行思路几乎一致:
text
调用外层函数
→ 创建一份局部状态
→ 返回一个引用该状态的函数
→ 后续调用返回的函数
→ 持续读取和修改这份状态
两个 Counter 会共享同一个 count 吗?
接着,我又做了一个很重要的实验:
go
package main
import "fmt"
func createCounter() func() int {
count := 0
return func() int {
count++
return count
}
}
func main() {
counterA := createCounter()
counterB := createCounter()
fmt.Println("A:", counterA())
fmt.Println("A:", counterA())
fmt.Println("B:", counterB())
fmt.Println("B:", counterB())
fmt.Println("A:", counterA())
}
最终运行结果:
text
A: 1
A: 2
B: 1
B: 2
A: 3
这一点很重要。
如果 counterA 和 counterB 共享同一份 count,结果应该是:
text
A:1
A:2
B:3
B:4
A:5
但实际结果并不是这样。
原因是:
go
counterA := createCounter()
执行了一次 createCounter(),产生了一份新的:
go
count := 0
接着:
go
counterB := createCounter()
又重新执行了一次 createCounter(),再次产生另一份:
go
count := 0
所以它们可以理解成:
text
counterA
└── 引用自己的一份 count
counterB
└── 引用自己的一份 count
两次对 createCounter 的调用分别创建了独立的状态,因此互不影响。
这让我想到前端里的"状态工厂"
JavaScript 中同样如此:
javascript
const counterA = createCounter();
const counterB = createCounter();
counterA(); // 1
counterA(); // 2
counterB(); // 1
counterB(); // 2
counterA(); // 3
如果写过 Vue Composition API,这种"每调用一次函数,就创建一份独立状态"的思想其实也很熟悉。
例如:
javascript
function useCounter() {
let count = 0;
return {
increase() {
count++;
},
getCount() {
return count;
},
};
}
每调用一次:
javascript
const a = useCounter();
const b = useCounter();
都可以拥有自己的一份状态。
当然,Vue 的响应式系统和 Go 闭包并不是一回事。
这里仅仅是从下面这个角度进行类比:
text
调用一个函数
→ 创建一份新的内部状态
→ 返回可以操作这份状态的函数
→ 不同调用之间的状态相互独立
这个思路在 JavaScript 中很常见,在 Go 中同样可以通过闭包实现。
函数不只可以返回,还可以作为参数传入
既然函数也是一种值,那么自然会有下一个问题:
函数能不能作为参数传给另一个函数?
可以。
于是我写了下面的代码:
go
package main
import "fmt"
func calculate(a, b int, operation func(int, int) int) int {
return operation(a, b)
}
func add(a, b int) int {
return a + b
}
func multiply(a, b int) int {
return a * b
}
func main() {
addResult := calculate(10, 20, add)
multiplyResult := calculate(10, 20, multiply)
fmt.Println("加法结果:", addResult)
fmt.Println("乘法结果:", multiplyResult)
}
运行结果:
text
加法结果: 30
乘法结果: 200
重点看这里:
go
operation func(int, int) int
operation 是一个函数参数。
它要求传入的函数必须满足下面的类型:
go
func(int, int) int
也就是:
text
接收两个 int
返回一个 int
所以:
go
func add(a, b int) int
符合要求。
下面这个函数:
go
func multiply(a, b int) int
也符合要求。
于是既可以写:
go
calculate(10, 20, add)
也可以写:
go
calculate(10, 20, multiply)
calculate 不关心具体要进行加法还是乘法。
它只负责:
text
接收两个数字
→ 接收一段符合要求的计算逻辑
→ 调用这段逻辑
→ 返回计算结果
传入的函数签名必须匹配
calculate 要求 operation 的类型是:
go
func(int, int) int
这意味着传入的函数必须满足:
text
参数数量相同
参数类型相同
返回值数量相同
返回值类型相同
例如,下面的函数无法直接传入:
go
func greet(name string) string {
return "你好," + name
}
因为它的函数类型是:
go
func(string) string
而 calculate 需要的是:
go
func(int, int) int
二者不是同一种函数类型。
所以 Go 不只是允许"把函数传进去",还会在编译阶段检查:
传入的函数是否符合参数要求的函数签名。
前端开发看到 callback 应该就懂了
这和 JavaScript 中的回调函数非常接近:
javascript
function calculate(a, b, operation) {
return operation(a, b);
}
function add(a, b) {
return a + b;
}
function multiply(a, b) {
return a * b;
}
calculate(10, 20, add);
calculate(10, 20, multiply);
TypeScript 的写法甚至和 Go 更像:
typescript
function calculate(
a: number,
b: number,
operation: (a: number, b: number) => number
): number {
return operation(a, b);
}
Go:
go
func calculate(
a, b int,
operation func(int, int) int,
) int {
return operation(a, b)
}
对比一下函数参数的类型。
TypeScript:
typescript
operation: (a: number, b: number) => number
Go:
go
operation func(int, int) int
两者都在描述:
operation必须是一个接收两个数字并返回一个数字的函数。
甚至可以直接把匿名函数传进去
不一定要提前定义:
go
func add(a, b int) int
也可以在调用 calculate 时直接传入匿名函数:
go
package main
import "fmt"
func calculate(a, b int, operation func(int, int) int) int {
return operation(a, b)
}
func main() {
result := calculate(10, 20, func(a, b int) int {
return a - b
})
fmt.Println("计算结果:", result)
}
运行结果:
text
计算结果: -10
如果换成前端代码:
javascript
calculate(10, 20, (a, b) => {
return a - b;
});
Go:
go
calculate(10, 20, func(a, b int) int {
return a - b
})
写到这里,已经非常像平时在 JavaScript 中使用的代码:
javascript
array.map((item) => ...)
array.filter((item) => ...)
array.find((item) => ...)
button.addEventListener("click", () => ...)
它们背后的共同思路都是:
text
把一段函数逻辑
→ 作为参数
→ 交给另一个函数
→ 由另一个函数在合适的时机调用
函数作为参数,不一定就是闭包
这里也需要再次区分几个概念。
下面的 add 是一个普通函数:
go
func add(a, b int) int {
return a + b
}
将它传给 calculate:
go
calculate(10, 20, add)
这里使用了"函数作为参数"的能力,但 add 并没有引用外层变量,所以它不是闭包。
而下面的匿名函数:
go
offset := 10
operation := func(a, b int) int {
return a + b + offset
}
引用了外层变量 offset,所以它形成了闭包。
如果把它传入:
go
result := calculate(10, 20, operation)
这里同时使用了:
text
匿名函数
闭包
函数作为参数
这些概念经常一起出现,但它们分别关注不同的问题:
text
匿名函数
→ 函数有没有名字?
闭包
→ 函数有没有引用外层作用域中的变量?
函数参数
→ 函数能不能作为值传给另一个函数?
高阶函数
→ 一个函数是否接收函数或返回函数?
我甚至自己写了一个类似 filter 的函数
学到这里,我顺手尝试模拟前端非常熟悉的 filter。
JavaScript:
javascript
const numbers = [1, 2, 3, 4, 5, 6];
const evenNumbers = numbers.filter((number) => {
return number % 2 === 0;
});
console.log(evenNumbers);
结果:
text
[2, 4, 6]
Go 版本:
go
package main
import "fmt"
func filter(numbers []int, condition func(int) bool) []int {
result := []int{}
for _, number := range numbers {
if condition(number) {
result = append(result, number)
}
}
return result
}
func main() {
numbers := []int{1, 2, 3, 4, 5, 6}
evenNumbers := filter(numbers, func(number int) bool {
return number%2 == 0
})
fmt.Println(evenNumbers)
}
最终输出:
text
[2 4 6]
看到结果以后,已经非常有 JavaScript filter 的感觉了。
不过,这段代码出现了几个目前还没有正式学习的内容:
text
[]int
range
append
所以这一篇暂时不展开它们。
现阶段只关注:
go
condition func(int) bool
这表示:
condition是一个接收int、返回bool的函数。
调用时传入的:
go
func(number int) bool {
return number%2 == 0
}
就相当于前端里的:
javascript
(number) => number % 2 === 0
filter 会把每个数字交给 condition:
text
condition(1) → false
condition(2) → true
condition(3) → false
condition(4) → true
condition(5) → false
condition(6) → true
只有返回 true 的数字会进入最终结果。
至于:
text
[]int 到底是什么?
为什么不是 [6]int?
range 是什么?
append 为什么能够增加元素?
这些问题留到下一阶段正式解决。
这里的 filter 只是用来验证:
Go 的函数也可以像 JavaScript 回调函数一样,被传入另一个函数并参与逻辑判断。
什么是高阶函数?
学到这里,其实已经在使用高阶函数了。
只要一个函数满足下面任意一种情况:
text
接收函数作为参数
或者
返回另一个函数
通常就可以称为高阶函数。
例如:
go
func createCounter() func() int
它返回另一个函数,所以 createCounter 是高阶函数。
再比如:
go
func calculate(
a, b int,
operation func(int, int) int,
) int
它接收函数作为参数,所以 calculate 也是高阶函数。
刚才写的 filter 同样如此:
go
func filter(numbers []int, condition func(int) bool) []int
它接收 condition 函数,因此也是高阶函数。
对于前端开发者来说,这个词应该并不陌生。
JavaScript 中的:
text
map
filter
reduce
sort
find
some
every
这些大量接收回调函数的 API,背后都使用了高阶函数的思想。
所以 Go 虽然没有直接使用:
javascript
numbers.filter(...)
这种写法,但语言本身完全支持把函数当作值进行保存、传递和返回。
可以给复杂的函数类型起名字
当函数类型比较简单时,直接写:
go
operation func(int, int) int
还比较清楚。
但如果同一种函数类型会重复出现,Go 也可以使用 type 给它起一个名字:
go
type Operation func(int, int) int
然后将 calculate 改成:
go
func calculate(a, b int, operation Operation) int {
return operation(a, b)
}
完整代码:
go
package main
import "fmt"
type Operation func(int, int) int
func calculate(a, b int, operation Operation) int {
return operation(a, b)
}
func add(a, b int) int {
return a + b
}
func main() {
result := calculate(10, 20, add)
fmt.Println("计算结果:", result)
}
这里的:
go
type Operation func(int, int) int
表示定义了一个名为 Operation 的函数类型。
以后看到:
go
operation Operation
就可以知道它需要的是:
text
接收两个 int
返回一个 int
这一篇暂时不继续展开自定义类型,只把它作为一个补充认识。
目前直接使用:
go
func(int, int) int
已经足够完成函数参数练习。
Go 和 JavaScript 的函数能力对照
这一阶段可以先整理成下面这张表:
表格
| Go | JavaScript / TypeScript |
|---|---|
func add() {} |
function add() {} |
f := func() {} |
函数表达式 |
| 没有箭头函数语法 | () => {} |
func() {}() |
IIFE |
| 闭包访问外层变量 | JavaScript 闭包 |
| 函数返回函数 | 函数返回函数 |
func(int) bool |
(value: number) => boolean |
| 函数作为参数 | callback |
| 接收或返回函数 | 高阶函数 |
type Operation func(...) |
TypeScript 函数类型别名 |
对于我来说,这是到目前为止,Go 和前端思维最接近的一部分。
这一阶段最容易混淆的几个概念
1. 匿名函数就是闭包吗?
不一定。
下面是匿名函数:
go
add := func(a, b int) int {
return a + b
}
它没有名称,但也没有引用外层变量。
下面的匿名函数引用了外层 count:
go
count := 0
increase := func() {
count++
}
它才形成了闭包。
所以:
text
匿名函数不一定是闭包
闭包也经常通过匿名函数创建
2. 函数可以像变量一样传递吗?
可以。
go
func add(a, b int) int {
return a + b
}
result := calculate(10, 20, add)
这里传入的是函数值:
go
add
而不是调用结果:
go
add(10, 20)
二者不要混淆:
text
add
→ 函数本身
add(10, 20)
→ 调用函数后得到的结果
3. 为什么传入 add 时不加括号?
因为 calculate 需要的是一个函数:
go
calculate(10, 20, add)
如果写成:
go
calculate(10, 20, add(10, 20))
第三个参数就不再是函数,而是 add 执行后得到的整数:
text
30
但 calculate 的第三个参数要求是:
go
func(int, int) int
不是 int。
4. 返回函数是什么意思?
下面的函数:
go
func createCounter() func() int
不会直接返回一个整数。
它返回的是另一个函数。
所以:
go
counter := createCounter()
此时 counter 是一个函数。
需要继续调用:
go
result := counter()
才能得到整数结果。
调用关系是:
text
createCounter()
→ 返回函数
counter()
→ 调用返回的函数
→ 得到 int
5. 每次调用 createCounter 都会共享状态吗?
不会。
go
counterA := createCounter()
counterB := createCounter()
createCounter() 分别执行了两次,也分别创建了两份独立的 count。
因此:
text
counterA 修改自己的 count
counterB 修改自己的 count
两者互不影响。
6. 什么是高阶函数?
如果一个函数:
text
接收另一个函数作为参数
或者
返回另一个函数
就可以称为高阶函数。
例如:
go
func createCounter() func() int
以及:
go
func calculate(a, b int, operation func(int, int) int) int
都是高阶函数。
这一阶段让我最大的感受
前面学习 Go 时,经常是在努力适应差异:
text
JavaScript 的 number
→ Go 分成 int、float64 等具体类型
JavaScript 常见的异常处理
→ Go 常见 result + error
前端中没有直接对应 defer 的语法
→ Go 可以在函数退出前执行延迟调用
到了闭包和高阶函数这里,方向突然反了过来。
不再是:
Go 为什么这样设计?
而是:
原来这个概念我早就在 JavaScript 里天天用了。
这也让我意识到,转语言的时候没必要把自己当成完全从零开始。
语法确实需要重新学习。
标准库、工程体系和运行机制也需要重新理解。
但是很多已经掌握的编程思想是可以继续复用的:
text
作用域
闭包
函数作为值
回调函数
高阶函数
状态封装
区别只是它们在另一门语言中的表达方式。
当然,这也不代表 Go 闭包和 JavaScript 闭包在所有细节上都完全相同。
目前只能说:
text
核心思想非常接近
→ 语法表达有所不同
→ 更深入的运行机制还需要以后继续学习
这一阶段的知识点总结
这次实际学习的内容可以整理成下面这张表:
表格
| 内容 | 示例 |
|---|---|
| 创建匿名函数 | func() {} |
| 函数保存到变量 | add := func(a, b int) int {} |
| 通过变量调用函数 | add(10, 20) |
| 立即执行匿名函数 | func() {}() |
| 立即执行时传参 | func(name string) {}("Starry") |
| 闭包访问外层变量 | func() { fmt.Println(count) } |
| 闭包修改外层变量 | func() { count++ } |
| 返回函数 | func createCounter() func() int |
| 独立闭包状态 | 多次调用 createCounter() |
| 函数作为参数 | operation func(int, int) int |
| 匿名函数作为参数 | calculate(..., func(...) ...) |
| 高阶函数 | 接收函数或返回函数 |
| 函数类型 | func(int, int) int |
整体学习路径是:
text
匿名函数
→ 函数保存到变量
→ 匿名函数立即执行
→ 函数访问外层变量
→ 闭包保存状态
→ 函数返回函数
→ 函数作为参数
→ 高阶函数
写在最后
这一阶段实际完成了:
text
匿名函数
→ 函数保存到变量
→ 匿名函数立即执行
→ 闭包访问外层变量
→ 闭包修改外层状态
→ 返回函数
→ createCounter
→ 多个闭包拥有独立状态
→ 函数作为参数
→ 匿名函数作为参数
→ 高阶函数
到这里,函数这一整个章节也算可以真正告一段落了。
从最开始:
go
func sayHello() {
}
一路学到了:
go
func createCounter() func() int
以及:
go
func calculate(
a, b int,
operation func(int, int) int,
) int
函数已经不再只是:
text
传入参数
→ 执行代码
→ 返回结果
函数本身也可以像数据一样:
text
保存
传递
返回
组合
而最后那个模拟 filter 的实验,也顺便给下一阶段留下了几个问题:
text
[]int
range
append
下一篇将正式进入 Go 的集合类型。
作为一个每天都在写下面这些代码的前端开发者:
javascript
const list = [];
list.push(...);
list.map(...);
list.filter(...);
我现在最想弄清楚的问题就是:
Go 的 Array 和 Slice,到底哪个才真正对应 JavaScript Array?
下一篇见。