序
当你拿着高薪却夜不能寐、辗转反侧的时候,你意识到,前端的精神食粮已经无法满足自己的心潮大海,卷进后端市场也许能让你的灵魂迈向新的升华,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 扩容规则
- 期望容量 > 旧容量 x 2,则直接使用期望容量作为新切片的容量
- 旧容量 小于 < 1024 时,扩容系数是 x2,即翻倍
计算公式:newCap = oldCap * 2- 旧容量 大于 >= 1024 时,扩容系数x1.25,即多25%
计算公式:newCap = oldCap + oldCap/4- append增加元素时,如果旧容量够用,则不再扩容,内存复用
-
go版本 1.18 扩容规则
- 期望容量 > 旧容量 x 2,则直接使用期望容量作为新切片的容量
- 旧容量 小于 < 256 时,扩容系数是 x2,即翻倍
计算公式:newCap = oldCap * 2- 旧容量 大于 >= 256 时,公式如下
计算公式:newCap = oldCap + (oldCap + 3 * 256)/4- 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里,我们更多的时候使用切片,那么,切片如何进行这些操作吗?
大叔暂时没发现官方有对这方面公开工具包,如果有朋友发现,请分享给我。为此,大叔本人造了一个轮子,因为实在是不想每次都去实现这些本就应该有的功能。于是,