代码随想录算法训练营第3天|链表Part01
在刷到 LeetCode 707「设计链表」题目时,我借此机会深入复习了 JavaScript 中的类(class
)机制。
过程中发现,class
语法表面看似简洁,但其底层原理与传统的函数构造器密切相关。
为了更加深刻地理解这一点,本文尝试从语法、机制和语言设计的角度系统梳理 JavaScript 中类的本质。
理解 JavaScript 类的本质:构造函数、语法糖与对象工厂
JS 中的类究竟是什么?
在 JavaScript 中,类并不是像 Java、C++ 那样的“类型定义”,而更像是一个「对象工厂」:一个用于生成对象的函数,外加一套方法共享机制。
1 | class MyClass { |
以上语法中的 constructor
其实是一个特殊的函数,它在用 new
创建实例时会被自动调用。这个类的本质可以还原为如下的传统构造函数写法:
1 | function MyClass(name) { |
这说明了:JavaScript 中的类,其本质仍是构造函数(Function),配合原型(prototype)来实现“方法共享”。
class 只是语法糖吗?
是的。从功能实现角度来说,class
是对构造函数 + 原型链的语法层封装,其核心行为并没有发生根本改变。
但 ES6 引入 class
之后,带来了一些细节变化,需要明确区分:
差异点 | 构造函数写法 | class 写法 |
---|---|---|
是否变量提升(hoisting) | ✅ 是函数,可提前访问 | ❌ class 不提升 |
是否可作普通函数调用 | ✅ Fn() 合法 |
❌ ClassName() 报错 |
是否自动启用严格模式 | ❌ 默认非严格 | ✅ 强制启用 strict mode |
方法是否可被枚举 | ✅ prototype 方法可枚举 | ❌ class 方法不可被枚举 |
尽管行为一致,语法糖依旧有其意义 —— 它使得面向对象编程更接近 Java 或 C#,也更具可维护性。
构造函数:不是结构体,但行为相似
我们常写的链表节点:
1 | function ListNode(val, next) { |
或者:
1 | class LinkNode { |
二者从功能上看是等价的:都定义了一个用于构造链表节点的工厂函数,并且都可通过 new
创建对象。
虽然 JS 没有“结构体”这个概念,但这些函数确实扮演了结构体构造器的角色:生成一组字段数据的对象,并可通过 prototype 共享方法。这种“结构体 + 方法”的组合体现了面向对象设计的基本思想。
函数也是对象吗?为什么可以被 new?
这部分常令人困惑,尤其是 JS 中「一切皆对象」的语言哲学。
1 | function foo() {} |
在 JS 中,函数是一种特殊类型的对象,具有以下属性:
- 可以拥有自己的属性(比如
foo.bar = 123
)。 - 可以作为构造函数通过
new
使用。 - 可以被赋值、作为参数传递、返回等等。
new
一个函数时,会经历以下过程(简化版):
1 | function MyConstructor(name) { |
即使函数体中什么都不写,也可以用 new
调用并得到一个空对象:
1 | function Empty() {} |
所以 new
并不依赖于函数体中是否使用了 this
,但只有使用 this
才能对实例对象进行赋值初始化。
建议掌握的关键知识点总结
概念 | 说明 |
---|---|
类的本质 | 构造函数 + 原型方法 |
构造函数与类的等价性 | 语法糖,但行为一致 |
new 的底层机制 |
创建对象 + 绑定原型 + 执行构造函数 |
函数是对象 | 可拥有属性,可被 new |
类方法的挂载位置 | 挂载在 prototype 上 |
class 的语义变化 | 更严格的行为,不能提升等 |
结语
JS 的类看似简单,但背后连接的是函数、对象、原型链等多个核心机制。掌握这些机制,不只是为了写出链表这样的题,更重要的是在构建组件、封装逻辑、掌握框架(如 React 中类组件)时,能深入理解其行为。
不要满足于“可以写 class”,而要能回答:“它的本质是什么?”