前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >你这只土拔鼠呀——前端眼中的golang

你这只土拔鼠呀——前端眼中的golang

作者头像
lhyt
发布2022-03-08 11:13:06
6320
发布2022-03-08 11:13:06
举报
文章被收录于专栏:lhyt前端之路lhyt前端之路

最近学习了golang,并在业务上开始使用。我们来用一名只会js/ts的前端视角,来快速熟悉一下go语言,10几分钟光速入门。简单的语法层面的不会多说,只从一些共同点突出点来出发。更深入的语言特性,自行根据文档去探索吧

多返回值

go一个函数可以返回多个值:

代码语言:javascript
复制
func A() (int bool) {
    return 1, false
}

一般用于返回一个操作的结果与错误:

代码语言:javascript
复制
func A(a int) (error int) {
    var (
        err error
        val int
    )
    val, err = getUserId(a)
    if err != nil {
        return err, 0
    }
    return nil, err
}

// 使用
err, val := A(100)

js里面的promise场景也有类似的用法,个人也有这种喜好:

代码语言:javascript
复制
export function awaitToJs<T, U = Error>(promise: Promise<T>): Promise<[U | null, T | undefined]> {
  return promise
    .then<[null, T]>((data: T) => [null, data])
    .catch<[U, undefined]>((err: U) => [err, undefined]);
}

// 使用
(async () => {
    const [err, data] = await getSomeInfo()
})()

方法与函数【重点】

go里面没有this,如何实现类似的效果?那就是方法了。go里面的方法,和函数的区别是,函数名字前面多了receiver。go的面向对象,其实也是如此。go里面对标js的plain object的,就是struct,而struct里面不能写函数,使用receiver来实现

代码语言:javascript
复制
// 比如我们定义一个类似js的map的功能
func (this Array0) ArrayMap(cb func(interface{}, int) interface{}) []interface{} {
    var ret []interface{}
    for i, v := range this {
        ret = append(ret, cb(v, i))
    }
    return ret
}

// 使用的时候test.ArrayMap来使用
func main() {
    test := Array0{{a: 10}, {a: 3}}
    fmt.Println(test.ArrayMap(func(item interface{}, index int) interface{} {
        return item.(struct{ a int }).a + index
    }))
}

// 定义一个Math类型,是一个结构体
type Math struct {
    E float64
}

// Math类型下有一个max方法
func (this *Math) max(values ...int) int {
    var ret int
    for _, v := range values {
        if v > ret {
            ret = v
        }
    }
    return ret
}
// 使用
func main() {
    var test Math
    test.E = 2.718281828459045
    fmt.Println(test.max(1, 2, 3, 4, 6)) // 6
}

interface

go的interface里面是一些方法的类型集合,它们是抽象的没有被实现的。接口里也不能包含变量。你如果给一个变量声明了interface类型,那么你要去实现它:

代码语言:javascript
复制
type GetInfo interface {
    GetName() string
    GetAge() int
}

type People struct {
    name string
    age  int
}

// 实现GetInfo
func (people *People) GetName() string {
    return people.name
}

func (people *People) GetAge() int {
    return people.age
}

func main() {
    var people GetInfo
    instance := new(People) // 实例化
    instance.name = "lhyt"
    instance.age = 100
    people = instance // 实现了GetInfo的instance
    fmt.Println(people.GetAge(), people.GetName())
}

但是一般业务代码中,用空interface较多。空interface类似ts的any的效果。

any => 空interface【重点】

基本介绍

空interface不包含任何方法,任何其他类型都实现了空接口,因此具有ts的any的效果。前面代码也看见了,有空interface。使用的时候如果想取值(你知道那是一个结构体/一个int),那么需要断言。如果断言失败,将会导致程序错误

回头看看这段代码:

代码语言:javascript
复制
fmt.Println(test.ArrayMap(func(item interface{}, index int) interface{} {
    return item.(struct{ a int }).a + index
}))

item是空interface类型(any),我们事先知道是一个struct,因此断言它就是struct{ a int },语法为anyValue.(someType)表示空interface类型的anyValue此处运行时类型为someType。当我们断言错误的时候:

代码语言:javascript
复制
fmt.Println(test.ArrayMap(func(item interface{}, index int) interface{} {
    return item.(int) + index
}))
// panic: interface conversion: interface {} is struct { a int }, not int

动态类型

那么问题来了,如果有真的多个类型存在的可能性呢?比如某个比较坑的第三方库,有时候返回个int有时候返回个float的。面对这种情况,go还是有办法的:

代码语言:javascript
复制
// 第三方库给的id有时候是string有时候是float64
func getIntIdFromStringOrFloat64(id interface{}) int {
    if _, ok := id.(string); ok {
        val, err := strconv.Atoi(id.(string)) // strconv很常用,做string和其他互转
        if err == nil {
            return val
        }
        panic(err)
    }
    return int(id.(float64))
}

这段表示,如果id断言为string成功,那么string转为int;如果断言到的是float64,那么float64转为int。经过我们的兼容,就不怕他们乱改类型了,我们的服务还是不会出事

补充一点,逗号ok模式是go里面很常用的,一般会接if一起用。表示做一些事情,返回成功或者错误,并根据有没有成功来做一些事情:

代码语言:javascript
复制
if _, ok := id.(string); ok {}

type-switch

如果有很多种类型,当然不会是像上面的写法那样子一层层if,go有专门的特殊的switch支持这种需求。type也可以是其他复杂的类型,如struct、map、slice、channel等

代码语言:javascript
复制
func getIntIdFromStringOrFloat64(id interface{}) int {
    switch id.(type) {
    case string:
        val, err := strconv.Atoi(id.(string))
        if err == nil {
            return val
        }
        panic(err)
    case float64:
        return int(id.(float64))
    }
    return id.(int)
}

业务代码中实现动态调用

比如有一个rpc客户端映射表,通过key去获取然后进行调用,那么大概会这样做:

代码语言:javascript
复制
type Rpc interface {
    Request(c *gin.Context, v ...interface{}) interface{}
}

func SomeService(c *gin.Context, key string) {
// ClientMap是map[string]Rpc类型
    RpcClients := ClientMap[key]
    if RpcClients == nil {
        return
    }
    Params := map[string]interface{}{
        "id": 666,
    }
    RpcClients.Request(c, Params)
}

对象 => 结构体/映射

go中的结构体/映射对标js的plain object了。但结构体和映射有一些不一样,结构体是需要提前知道且确定好每一个字段,做不到动态;而map就可以做到动态增减key-value对。取值的时候,结构体可以通过.,而map需要["someKey"]

代码语言:javascript
复制
type Hello struct {
    a int
    b string
}

type World map[string]string
type World1 map[bool]string

// 结构体
test1 := Hello{1, "hey"}
// map,key为string
test2 := World{"a": "1", "b": "2"}
// key为bool
testc := World1{false: "1", true: "2"}
// 注意取值方式区别
fmt.Println(test1.a, test2["a"], testc[false])

结构体做不到后续新增key了,map却可以,map取不到的话返回nil 。类似的,js的数组对标go的切片/数组,go数组也是需要提前知道有什么元素,而slice类似map一样,可以动态维护元素

try-catch => panic/recover

js中使用try-catch捕获错误,go的话,类型上的错误在编译阶段即可抛出,剩下的就是那些动态的、运行时报错了。运行时报错在go里面叫panic——程序恐慌

代码语言:javascript
复制
func exec(g func()) {
    defer func() {
        if err := recover(); err != nil {
            fmt.Println("错误: %v", err)
        }
    }()
    g()
}

当运行时出错,将会panic。recover是指从panicError中恢复,让程序可以重新获得控制权,停止终止过程进而恢复正常执行。类似的js代码:

代码语言:javascript
复制
function exec(f) {
    try {
        f()
       } catch (e) {
        console.log('错误:', e)  
    }
}

toString类型转换 => stringer

go也有类似js的类型转换toString。js中默认的把对象转字符串是[object Object],数组转字符串是隐式调用join,或者可以手动修改Symbol.toPrimitive方法。go里面类似手动重写toString的方式就是stringer了。fmt包自带

代码语言:javascript
复制
type Stringer interface {
    String() string
}

在fmt打印的时候打印字符串,如果打印的是struct,则会走系统默认打印出{value1, value2}集合。我们想自定义这个过程,需要自己去实现String方法:

代码语言:javascript
复制
type Person struct {
    Name string `json:"name"`
    Age  int    `json:"年龄"`
}

func (p Person) String() string {
    data, err := json.Marshal(p)
    if err == nil {
        return string(data)
    }
    panic(err)
}

func main(){
    test := Person{"lhyt", 100}
    fmt.Println(test)
    // {"name":"lhyt","年龄":100}
    // 不修改的情况是{lhyt, 100}
}

此外,一个结构体里面后面接json:"name"表示在json序列化的时候,key是这个。这是go的结构体标签,你可以理解为一些字段描述信息,运行时可以通过反射读取到这些信息,做一些对应的逻辑

reflect

go的运行时动态相关的逻辑很多就靠反射来实现了。比如js的object.keys的go的实现:

代码语言:javascript
复制
type World map[string]string

test2 := World{"a": "1", "b": "2"}
v := reflect.ValueOf(test2)
fmt.Println(v.MapKeys(), "<<<")
// [a b]

知道了keys,那么object.values/entries都可以实现了

还可以做很多很有趣的动态的事情,看看v它的提示有啥:

最后

go的特色和深入这里不多说了,比如协程,有兴趣的移步这里。当然还有一些前端开发的共同点,可以从前端的视角去快速熟悉,比如go的hmr——air,go的包管理——go mod等。单机玩go的话,可以装个air热更新跑起来即可,包括我现在单机测试也是这样

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2020/11/14 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 多返回值
  • 方法与函数【重点】
  • interface
  • any => 空interface【重点】
    • 基本介绍
      • 动态类型
        • type-switch
          • 业务代码中实现动态调用
          • 对象 => 结构体/映射
          • try-catch => panic/recover
          • toString类型转换 => stringer
          • reflect
          • 最后
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档