和JS对比

设计

工程化上更加完备: 自带测试框架、性能分析工具、汇编语言预览等

提供系统底层能力: 用runtime包控制线程调度、用unsafe包读写数据的内存地址和内容等

局部变量

在JS里写if时经常有如下代码:

const sum = a + b;
if (sum > max) {
  handle(sum);
} 

或者:

if (a + b > max) {
  handle(a + b);
}

前者污染外部作用域(在if外部作用域定义了一个sum,实际上这个sum可能只会在if中使用),后者有重复代码(a+b写了两次)。

go可以这么写来定义一个局部变量sum,该变量仅能在if中被访问:

if sum := a + b; sum > max {
  handle(sum)
}

函数传参方式

go定义函数时可以指定参数是按引用还是按值(副本)传递

func ByVal(arr [3]int, num int) {
  arr[1] = 0
  num = 0
  // 修改的只是arr、num副本
}

func ByRef(arr *[3]int, num *int) {
  (*arr)[1] = 0
  *num = 0
  // 可修改外部的arr、num值
}

func main() {
  arr := [3]int{1,2,3}
  num := 1
  ByVal(arr, num)
  fmt.Print(arr, num) // [1 2 3] 1
  ByRef(&arr, &num)
  fmt.Print(arr, num) // [1 0 3] 0
}

arr改成切片类型时,情况会有点变化:

func ByVal(arr []int) {
  arr[1] = 0
}

func main() {
	arr := []int{1,2,3}
	ByVal(arr)
	fmt.Print(arr) // [1 0 3]
}

和上述例子的区别在于把[3]int改成了[]int,结果arr的值就改变了。

因为[]int是切片类型,传递到ByVal内部时,同样会创造一个切片副本,但这个arr副本其内部指向的底层数组和函数外部arr是同一个,所以函数内部对arr[1]的改动会直接影响底层数组,其本质还是按值传递的。

值比较

基本类型间比较 - 直接比较 struct间比较 - 对各个字段比较,如果有不可比较字段则编译报错

组合替代继承

GO中的继承不同于类继承、原型继承,甚至不像继承,更像是组合。

struct A需要复用已有struct B的字段时,直接把B写到A的struct声明里,还可以把C、D、E等更多struct一起组合到A里,使得A获得这些struct中的字段。

如果遇到对一系列对象遍历并调用其方法的场景,一般来说这些对象需要是同一个类,才有共同的类方法,但GO中没有类,用interface来模拟类的行为,这一系列对象不管它们各自的结构是怎样,只要它们都实现了interface中定义的方法,它们就可以当做是一个『类』,就可以放在循环里统一调用interface里的方法。

组合+接口相比继承的好处:子类不需要知晓父类的实现细节;可运行时动态修改接口背后的实现;更易于复杂的拓展。

错误处理

JS中的错误(throw)和异常都可以用try-catch来处理,并且有冒泡机制。

GO在设计上把错误(error)和异常(panic)分开对待。

panic也会冒泡,可以被上层的defer中的recover()捕获到,若没有被捕获,整个程序会退出。

func crash() {
	arr := [4]int{1,2,3,4}
	for i := 0; i < 5; i++ {
		arr[i] = 0 // panic: arr[5] is out of range
	}
}

func main() {
	defer (func() {
		err := recover()
		if err != nil {
			fmt.Print(err) // runtime error: index out of range [4] with length 4
		}
	})()
	crash()
	fmt.Print("done") // never print
}

error则被作为函数返回值返回,强制开发者在第一现场处理(或者忽略),error是一个interface类型。

type error interface {
    Error() string
}

协程

协程详见协程

go在语言层面自己实现了协程goroutine,实现了并行和低资源占用。

goroutine间通过通道通信。

垃圾回收

标记-清除法(三色标记) + 写屏障

三色标记:

全部对象默认为白,从root开始,把引用的对象标记为灰,把灰对象的引用的非黑对象继续标记为灰,并把原来的灰对象标记为黑表示已遍历,重复这个过程,直到没有灰对象,剩下的所有白对象则表示没被引用到,销毁全部白对象。

写屏障:GC过程并不STW,程序仍在执行,仍可以修改对象间的引用。GC过程中监听对象修改,将受改动影响的对象直接标记为灰,继续GC。