一个前端的 Golang 语法层面入门感悟(持续补充)

写本文主要想对比下 Golang 和通常前端所用的日常开发语言,如 JS 和 TS 之间的异同,以便更好地切换开发时的心智模式。

关于变量作用域

Golang 的作用域好像与 ES6 的 let 变量作用域相同。

The Way To Go 中的例子

  1. 输出 GOG
package main

var a = "G"

func main() {
   n()
   m()
   n()
}

func n() { print(a) }

func m() {
   a := "O"
   print(a)
}
  1. 输出 GOO
package main

var a = "G"

func main() {
   n()
   m()
   n()
}

func n() {
   print(a)
}

func m() {
   a = "O"
   print(a)
}
  1. 输出GOG
package main

var a string

func main() {
   a = "G"
   print(a)
   f1()
}

func f1() {
   a := "O"
   print(a)
   f2()
}

func f2() {
   print(a)
}

注意 f2() 中的 print(a),读的是全局变量a,而不是函数执行域中f1()a

关于包导出

Golang 通常一个目录为一个包(package)。不像 JS 中常见的包管理机制(如 ESM等),Golang 包内成员不需要用export等关键字显式声明导出。

导出的成员函数应以大写字母开头,否则不可以导出(Goland IDE 如果引用了包内小写的函数,会有报错提示,点击导出后 IDE 会将函数开头改成大写字母)。

三、变参函数

Golang 的变参函数类似于解构赋值,但有略微不同。如 The Way To Go 中的例子。

  1. 当变参函数想要拿到剩余变长参数所组成的切片,在表示切片类型时,类似解构赋值的三点运算写在前面:
func Greeting(prefix string, who ...string) // ...string 表示 []string 这一切片类型
Greeting("hello:", "Joe", "Anna", "Eileen")
  1. 当将切片传递给变参函数时,类似解构赋值的三点运算写在切片变量后面:
package main

import "fmt"

func main() {
    x := min(1, 3, 2, 0)
    fmt.Printf("The minimum is: %d\n", x)
    slice := []int{7,9,3,5,1}
    x = min(slice...) // 写在后面
    fmt.Printf("The minimum in the slice is: %d", x)
}

func min(s ...int) int {
    if len(s) == 0 {
        return 0
    }
    min := s[0]
    for _, v := range s {
        if v < min {
            min = v
        }
    }
    return min
}

关于结构体的类型

我们知道在 TypeScript 中,对象的类型只要形状一致,即认为该对象属于某一类型:

type Options = {
  par1: string;
  par2: number;
  par3: any;
}

const instance1: Options = {
  par1: "aaa",
  par2: 123,
  par3: "bbb",
}

const instance2 = {
  par1: "aaa",
  par2: 123,
  par3: "bbb",
}

const instance3: Options = instance2; // 无报错

但在 Golang 中并非如此。例子在下面:

varargs.go

package varargs

import (
  "fmt"
)

type Options struct {
  Par1 string
  Par2 int64
  Par3 interface{}
}

func PrintVarArgsUsingStruct(para *Options) {
  fmt.Print("\n\nPrintVarArgsUsingStruct: ", *para)
}

sturcttest.go

package structtest

import (
  "fmt"

  "goSimplePractice/varargs"
)

func Test() {
  fmt.Print("\n\nstructTest: ")

  varMadeFromType := &varargs.Options{Par1: "123", Par2: 19980416, Par3: "testTestTest"}
  varMadeFromStruct := &struct {
    Par1 string
    Par2 int64
    Par3 interface{}
  }{Par1: "123", Par2: 19980416, Par3: "testTestTest"}

  varargs.PrintVarArgsUsingStruct(varMadeFromType)

  // varargs.PrintVarArgsUsingStruct(varMadeFromStruct)
  // Output: Cannot use 'varMadeFromStruct' (type *struct {...}) as the type *Options

  varargs.PrintVarArgsUsingStruct((*varargs.Options)(varMadeFromStruct)) // ✅
}

事实上,在 golang 等强类型语言中无形状这一说法,因为 golang 不是动态类型语言,无需为兼容弱类型的对象做妥协。但有完全相同「形状」,即完全相同的字段(名字、个数和类型)的结构体可以互相转换。