前端学后端,是时候抢个沙发了(golang)

494 阅读7分钟

当你拿着高薪却夜不能寐、辗转反侧的时候,你意识到,前端的精神食粮已经无法满足自己的心潮大海,卷进后端市场也许能让你的灵魂迈向新的升华,golang,不求卷土来袭,但求坐上沙发。

                    ————比尔·伯格·布克

高龄大叔的我,依然渴望驾着小船在大海中与你们并肩作战,无奈那岁月的伤痕在那皎洁的月光下沉默的摩擦。只希望这夜深人静的月色能够助我们风雨无阻,愿那前途的光明引我们走向豁然的前方。不屈不挠,永不落后。

                    ————大叔你好

目标

今天我就把我学习golang的基础成果分享给大家,通过js/ts(下面统一用ts称谓)与golang的语法对比,来加速你对golang的理解与学习

golang版本号

version:1.18

分号:

  • ts 已经不是必须的了
  • go 不能写

变量定义与赋值

  • ts

    let a = 100 //var已经不推荐使用了
    a =  200 //正确
    a = 200.01 //也没问题
    
  • golang

    • 变量再次赋值必须是相同类型
    • var定义可以简写成 :=
    var a int = 100 //可以简写成下面,因为go会自动类型推断
    var a = 100//如果用关键字定义只有var
    a = 200
    a = 100.1//报错,因为类型不同
    a = "100"//报错,因为类型不同
    

    或者简写成

    a:= 100 //这种方式必须在函数体里,如果想在函数体外,则只能用var定义
    a = 200 //必须相同类型,类型不同会报错
    a = "200"//报错,因为类型不同
    
    b:= "efg"
    b = "ght" //正确
    b = 'ght'//报错,因为只能用双引号
    

函数

  • ts
    //关键字定义
    function requestApi(apiUrl: string) : boolean {
        function request() {//函数体里可以再次用function定义函数
    
        }
        requestApi()//执行
        return true
    }
    const requestApi = ()=>{//箭头函数方式
    
    }
    
  • golang
    • 关键字变短了 function 变成了 func
    • 参数类型没有了冒号
    var b int = 20 //定义后立刻赋值没问题
    var a int
    a = 100//报错,只能在函数体里赋值
    c:= 10//报错,简写,只能在函数体里使用
    func requestApi(apiUrl string) bool {//函数定义方式一
        a = 100 //函数体里赋值,没问题
        c:= 100 //正确
        
        //只能通过匿名函数赋值给变量的方式实现函数里套函数
        var request = func() {
    
        }
        request()
        func() {//立刻执行
    
        }()
        return true
    }
    
    //函数定义方式二,通过匿名函数给变量赋值方式
    var requestApi = func(apiUrl string) {
    
    }
    
    • golang禁止在func函数体里再用func二次定义函数,下面是错误示范,只能通过上述匿名函数赋值变量的方式
    func requestApi(apiUrl string) {
       //报错,因为函数里禁止使用func进行二次定义func request(){ ❌ 
       
       }
    }
    
    

基本类型

  • ts

    • boolean
    • string
    • number
    • void
    • null
    • undefined
    • []
    • Record<string, number>
  • golang

    • bool
    • string
    • int (整型)(常用)
    • int8
    • int16
    • int32
    • int64
    • uint8(无符号整型)
    • uint16
    • uint32
    • uint64
    • float32(浮点类型)
    • float64
    • complex64(复数)
    • complex128
    • error(错误)
    • rune(相当于int32)
    • byte(相当于uint8)

数组

  • ts:

    • ts里只有数组一个称谓
    • 类型在方括号前面
    const x: number[] = [1, 2, 3]
    x.push(4)
    
  • golang

    • 定长数组:长度是固定的,创建后不可修改长度,即不能扩容
    • 类型在方括号后面
    • 赋值要用花括号{}
    x:= [2]int{1,2,3}❌ //报错,规定长度为2,实际给了3个
    x:= [3]int{1,2,3}✅ //定长数组
    
    //报错,定长数组不能扩容(append类似于js里的push)
    x = append(x, 4) ❌ 
    
    • 自动定长数组
    x:= [...]int{1,2,3}//比事先给定长度值更方便一些
    x = append(x, 4)❌ //报错,定长数组不能扩容
    
    • 切片
      • 把上面定长数组[3]或[...]的内部掏空就是切片 []
      • 切片的底层也数组实现的
      • cap:表示容量,可以理解为只有三张床的房子,能住3个人,也能住2个人
      • len:表示长度,可以理解为人数,如果现在是4个人,就得加床(扩容)
    //代码示范 版本号 1.18
    x := []int{1, 2, 3}
    fmt.Printf("len of x: %v\n", len(x)) // 长度 3
    fmt.Printf("cap of x: %v\n", cap(x)) // 容量 3
    x = append(x, 4) //扩容 类似于js里的 push
    fmt.Printf("len of x: %v\n", len(x)) //4
    fmt.Printf("cap of x: %v\n", cap(x)) //6
    

细心的朋友可能看出问题了,为什么扩容之后,容量是6,而不是4?这就涉及到golang的切片扩容规则了,先看两个概念

  • 旧容量:扩容前的容量(元素个数)

  • 期望容量:即将扩容后的容量(元素个数)

  • go版本 1.17 扩容规则

    1. 期望容量 > 旧容量 x 2,则直接使用期望容量作为新切片的容量
    2. 旧容量 小于 < 1024 时,扩容系数是 x2,即翻倍
    计算公式:newCap = oldCap * 2
    
    1. 旧容量 大于 >= 1024 时,扩容系数x1.25,即多25%
    计算公式:newCap = oldCap + oldCap/4
    
    1. append增加元素时,如果旧容量够用,则不再扩容,内存复用
  • go版本 1.18 扩容规则

    1. 期望容量 > 旧容量 x 2,则直接使用期望容量作为新切片的容量
    2. 旧容量 小于 < 256 时,扩容系数是 x2,即翻倍
    计算公式:newCap = oldCap * 2
    
    1. 旧容量 大于 >= 256 时,公式如下
    计算公式:newCap = oldCap + (oldCap + 3 * 256)/4
    
    1. append增加元素时,如果旧容量够用,则不再扩容,内存复用
  • 扩容规则举例(基于go 1.18)

//扩容规则(一)
x := []int{1, 2, 3} 
fmt.Printf("len of x: %v\n", len(x)) // 长度 3 
fmt.Printf("cap of x: %v\n", cap(x)) // 容量 3 
x = append(x, 4, 5, 6, 7, 8, 9, 10) //扩容 7个,期望容量为 10,10 > 3 * 2
fmt.Printf("len of x: %v\n", len(x)) //10 
fmt.Printf("cap of x: %v\n", cap(x)) //10 满足规则(一)

//扩容规则(二)
x := []int{1, 2, 3} 
fmt.Printf("len of x: %v\n", len(x)) // 长度 3 
fmt.Printf("cap of x: %v\n", cap(x)) // 容量 3 
x = append(x, 4) //扩容 1个,期望容量为 4,旧容量为 3 < 256,3 x 2 = 6
fmt.Printf("len of x: %v\n", len(x)) //4 
fmt.Printf("cap of x: %v\n", cap(x)) //6 满足规则(二)
x = append(x, 5, 6)
fmt.Printf("len of x: %v\n", len(x)) //6
fmt.Printf("cap of x: %v\n", cap(x)) //6 因为旧容量够用,无需扩容
x = append(x, 7)//旧容量为6,乘以2等于12 
fmt.Printf("len of x: %v\n", len(x)) //7
fmt.Printf("cap of x: %v\n", cap(x)) //12 满足规则(二)

//扩容规则(三)内容太多,不便举例

泛型变量

  • ts
    • 泛型变量定义时用尖括号包起来
function requestApi<T>(apiUrl: T, ...args: string[]): T {
  return apiUrl
}

  • golang 1.18后才完全支持泛型
    • 泛型变量用方括号包起来
    • 必须给默认值,如果不知道,可以使用any
    • go泛型没有ts那么复杂炫技,只是常规用法
func requestApi[T any](apiUrl T, args ...string) T {
	return apiUrl
}

面向对象

  • ts 它可以使用class关键字,使用起来比较舒服
class People {
  public name: string
  constructor(name: string) {
    this.name = name
  }
  public eat = () => {

  }
  private run = () => {

  }
}
const p = new People("韩梅梅")
console.log(p.name)
p.eat()
  • golang(没有class关键字,通过结构体struct + 接收器 来实现)
根目录,创建一个文件夹 people,里面创建一个文件people.go,结构如下:

根目录
    - people文件夹 
           -- people.go
           -- 其他 go 文件
    - main.go       入口执行文件
    - go.mod        golang的包管理工具
在 people.go 文件里:

package people       //包名
type People struct { // 定义一个结构体
    //如果这个字段想被 people.go以外的其他文件import后使用,
    //则第一个字母必须大写
    Name string
    age  int
}

func New(name string, age int) People {//模拟构造函数
    p := People{
        Name: name,
        age:  age,
    }
    return p
}

//(person *People) 被称为接收器,固定语法
func (person *People) Eat() { // 封装方法 首字母大写,外面可调用

}

//(person *People) 被称为接收器,固定语法
func (person *People) run() { // 封装方法 首字母小写,外面不可调用

}

go.mod文件里
    module xxx.com/projectName
    go 1.18
在main.go文件里
import (
	"fmt"
	"xxx.com/projectName/people"
)
func main(){
    p := people.New("韩梅梅", 16) //我才16岁吗?
    fmt.Printf("p.name: %v\n", p.Name)
    fmt.Printf("p.age: %v\n", p.age) ❌ // 报错,因为第一个字母为小写,所以这里无法调用
    p.Eat() // 因为第一个字母为大写,所以外面可以调用
    p.run() ❌ // 报错,因为第一个字母为小写,所以这里无法调用
}

结束语

golang的基础还是不少的,今天就简单介绍到这里。希望前端大佬们能够通过这次分享快速对比两种语言的语法,少走弯路

这里提出一个疑问,在js里,我们可以使用各种数组操作方法,map filter splice,但是golang里,我们更多的时候使用切片,那么,切片如何进行这些操作吗?

大叔暂时没发现官方有对这方面公开工具包,如果有朋友发现,请分享给我。为此,大叔本人造了一个轮子,因为实在是不想每次都去实现这些本就应该有的功能。于是,