遗嘱、水管与抢救室:TS 切入 Go 的流程控制、接口与并发

0 阅读3分钟

🚀 省流助手(速通结论)

  • Defer 扫尾:它是函数的“遗愿”,锁定的是函数作用域而非代码块。后进先出(LIFO)执行。
  • 接口纯粹性:Go 接口严禁包含变量。它只定义行为,且实现是隐式的(不需要 implements)。
  • 切片不是数组:Slice 是底层内存的窗口。扩容会触发“搬家”,不注意 copy 会导致数据人格分裂。
  • 并发解耦:WaitGroup 负责同步,Channel 负责传球。不要通过共享内存来通信。

1. Defer 扫尾机制:它是“遗嘱”而非 finally

在 TS 中,finally 紧跟在 try 代码块之后。但在 Go 中,defer 是函数级的延迟调用。

TypeScript(代码块级收尾)

async function writeInfo() {
    try {
        const file = await openFile();
        // ... 逻辑 A
    } finally {
        file.close(); // 块结束立刻执行
    }
    // ... 逻辑 B (此时文件已关闭)
}

Go(函数级遗嘱)

func writeInfo() {
    file, _ := os.Open("test.txt")
    // defer 锁死的是整个函数。即使逻辑 B 还在跑,file 也不会关
    defer file.Close() 

    // 如果逻辑多,必须包装成匿名函数并显式调用 ()
    defer func() {
        fmt.Println("开始清理多项资源")
        // 复杂收尾逻辑...
    }() 
}

🪝 思维钩子:defer 像是在函数出口处“埋雷”。多个 defer 会像堆盘子一样后进先出(最后声明的先执行)。


2. 接口的行为契约:严禁携带“私货”

在 TS 中,interface 既可以定义方法也可以定义属性(变量)。但在 Go 中,接口是纯粹的行为契约。

TypeScript(混合定义)

interface ReadWriter {
    readonly id: number; // ✅ 合法:可以包含属性
    read(): void;
}

Go(纯粹行为)

type ReadWriter interface {
    // b int // ❌ 编译报错:接口不能包含数据字段
    Read()
    Write()
}

🪝 思维钩子:Go 接口只关心“你能做什么”,而不关心“你长什么样”。想定义属性?请回 struct。这种纯粹性让 Go 的隐式实现(只要方法对上,就自动实现接口)变得异常强大。


3. 切片的动态魔术:小心“窗口”背后的陷阱

TS 开发者常把 Slice 当成普通 Array。实际上,它是指向底层内存的一个带容量描述的窗口。

TypeScript(切片即副本)

const original = [1, 2, 3];
const sub = original.slice(0, 2); 
sub[0] = 99;
console.log(original[0]); // 1 (原数组不受影响)

Go(切片即视图)

original := []int{1, 2, 3}
sub := original[0:2] // sub 是原内存的“窗口”
sub[0] = 99
fmt.Println(original[0]) // ⚠️ 99 (原数组被同步修改了!)

// 💡 只有执行 copy() 才是真正的“深拷贝”

🪝 思维钩子:append 操作是分水岭。如果容量(Cap)够,它改原件;如果容量不够触发扩容,它会偷偷“搬家”并断开与原数组的联系。


4. 并发等待的范式:从 Promise 到通道

TS 靠 Promise.all 监听状态,Go 靠 sync.WaitGroup 计数或 Channel 传球。

TypeScript(状态监听)

// 并行运行,主线程通过 Promise 状态获知结束
await Promise.all([task1(), task2()]);

Go(计数同步)

var wg sync.WaitGroup

// 任务抽离为独立函数时,必须传递指针 *sync.WaitGroup
func doTask(i int, wg *sync.WaitGroup) {
    defer wg.Done() // 任务完成,计数减一
    fmt.Println(i)
}

func main() {
    wg.Add(2) // 显式计数
    go doTask(1, &wg) // 开启独立协程
    go doTask(2, &wg)
    wg.Wait() // 阻塞直到计数归零
}

🪝 思维钩子:go 关键字开启的是一个并行时空。WaitGroup 是你的“考勤表”,而 Channel 是不同时空间互通有无的“输油管”。


结语:
通过这三篇的“直觉对手戏”,你已经完成了从 TS 到 Go 的底层思维重构。Go 的魅力不在于语法糖,而在于那份极致的确定性。

祝你在 Gopher 的世界里,写出像水一样清澈的代码。