对象模型与内存的“钥匙理论”:TS 切入的 Go 的结构体与指针

0 阅读4分钟

🚀 省流助手(速通结论)

  • Struct 不是类:它只存数据,不存逻辑。逻辑通过“接收者(Receiver)”外挂实现,类似 JS 的 prototype
  • 没有 this:Go 显式命名接收者(如 u),彻底解决了 TS 中 this 指向丢失的世纪难题。
  • 权限全靠“吼”:首字母大写 = public,首字母小写 = private。这是编译器级别的强制隔离。
  • 赋值即复印:a := b 是物理深拷贝。想要引用?必须显式使用 & 拿钥匙(指针)。
  • 契约严苛:函数要求指针(*User)时,你传值(User)会直接编译报错,Go 不玩隐式转换。

1. 结构体不是类:数据与逻辑的彻底解耦

在 TS 中,class 是高度内聚的。但在 Go 中,struct 被剥离得只剩下数据描述。

TypeScript(内聚模型)

class User {
    name: string;
    constructor(name: string) { this.name = name; }
    say() { console.log(this.name); } // 逻辑住在类里面
}

Go(数据模型)

type User struct {
    Name string // 仅定义数据字段
}

// 逻辑通过外部函数“外挂”到 User 上
func (u User) Say() {
    fmt.Println(u.Name)
}

🪝 思维钩子:Go 强迫你把“长什么样”和“能干什么”分开。你可以把 struct 看作是去掉了方法实现的“瘦身版” Class。


2. 方法的外挂艺术:再见了,迷之 this

TS 开发者最头疼的莫过于 this 绑定丢失。Go 根本不设 this 关键字,而是让你显式命名。

TypeScript(this 绑定焦虑)

const u = new User("Alice");
const say = u.say;
say(); // ❌ 报错:this 指向丢失(除非 bind 或用箭头函数)

Go(显式绑定)

// u 只是一个变量名,你可以叫它 self, me 或者 u
func (u User) Say() {
    fmt.Println(u.Name) // u 的指向在定义时就写死了,永远不会丢
}

🪝 思维钩子:Go 的方法绑定极像 User.prototype.say = ...。这种设计让逻辑复用变得极其简单,且没有运行时的上下文陷阱。


3. 权限的大小写命令:最简单的隐藏规则

在 TS 中,我们写 private。在 Go 中,你只需按下 Shift 键。

TypeScript(关键字控制)

class User {
    public name: string;
    private age: number; // 靠关键字限制访问
}

Go(首字母定生死)

type User struct {
    Name string // 首字母大写:外部包可见 (Public)
    age  int    // 首字母小写:仅限本包可见 (Private)
}

🪝 思维钩子:这是 Go 的“行政命令”。如果你在 A 包定义了 age,在 B 包里你连提示都敲不出来。调用处必须严格遵守定义处的大小写,没有模糊地带。


4. 指针、原件与复印机:a := b 的终极实验

这是 TS 程序员转 Go 时最容易产生 Bug 的地方。在 TS 里,对象赋值是引用;在 Go 里,一切赋值皆拷贝。

TypeScript(自动引用)

const u1 = { name: "Alice" };
const u2 = u1; 
u2.name = "Bob";
console.log(u1.name); // "Bob" (两人共用一个地址)

Go(默认复印机模式)

u1 := User{Name: "Alice"}
u2 := u1      // 发生物理拷贝!u2 是 u1 的一个完整副本
u2.Name = "Bob"
fmt.Println(u1.Name) // "Alice" (u1 根本没动)

u3 := &u1     // 这才是拿到了 u1 的钥匙(指针)
u3.Name = "Bob"
fmt.Println(u1.Name) // "Bob" (原件被修改了)

🪝 思维钩子:a := b 是盖了一座一模一样的房子;a := &b 是把房子的钥匙交出去。


5. 契约与传参:强指针约束

💡 注意:为了从 TS 视角更好理解 Go 的严谨性,请看下表。在 Go 中,类型契约是不可妥协的。

场景Go 表现思维入口
函数要求 *User(指针)你传 User(值)❌ 编译报错:类型不匹配
函数要求 User(值)你传 *User(指针)❌ 编译报错:原件不能直接塞进复印机

🪝 思维钩子:Go 不支持隐式地址转换。当你写下 & 的那一刻,你在提醒自己:“我要交出修改原件的权限了”。


6. 工程组织:go.mod 对标 package.json

Go 的依赖管理不再有臃肿的 node_modules,它更清爽,但也更严格。

  • 导入逻辑:Go 导入的是文件夹(Package),而不是单个文件。

  • 版本管理:

    • go.modpackage.json(记录主依赖版本)
    • go.sumpackage-lock.json(记录哈希,防止内容篡改)

🪝 思维钩子:运行 go mod tidy 就像 npm install。它会自动扫描代码中的 import 路径,下载缺失包并剔除冗余包。


下篇预告:
我们将进入 Go 语言最迷人的部分:并发(Goroutine) 与 通道(Channel)。为什么 Go 处理万级并发轻而易举?为什么接口里不能定义变量?我们下篇见。