代码随想录算法训练营第3天|链表Part01

代码随想录算法训练营第3天|链表Part01

在刷到 LeetCode 707「设计链表」题目时,我借此机会深入复习了 JavaScript 中的类(class)机制。 过程中发现,class 语法表面看似简洁,但其底层原理与传统的函数构造器密切相关。 为了更加深刻地理解这一点,本文尝试从语法、机制和语言设计的角度系统梳理 JavaScript 中类的本质。

理解 JavaScript 类的本质:构造函数、语法糖与对象工厂

JS 中的类究竟是什么?

在 JavaScript 中,类并不是像 Java、C++ 那样的“类型定义”,而更像是一个「对象工厂」:一个用于生成对象的函数,外加一套方法共享机制。

1
2
3
4
5
6
7
8
9
class MyClass {
constructor(name) {
this.name = name;
}

greet() {
console.log('Hello, ' + this.name);
}
}

以上语法中的 constructor 其实是一个特殊的函数,它在用 new 创建实例时会被自动调用。这个类的本质可以还原为如下的传统构造函数写法:

1
2
3
4
5
6
7
function MyClass(name) {
this.name = name;
}

MyClass.prototype.greet = function () {
console.log('Hello, ' + this.name);
};

这说明了:JavaScript 中的类,其本质仍是构造函数(Function),配合原型(prototype)来实现“方法共享”。

class 只是语法糖吗?

是的。从功能实现角度来说,class 是对构造函数 + 原型链的语法层封装,其核心行为并没有发生根本改变。

但 ES6 引入 class 之后,带来了一些细节变化,需要明确区分:

差异点 构造函数写法 class 写法
是否变量提升(hoisting) ✅ 是函数,可提前访问 ❌ class 不提升
是否可作普通函数调用 Fn() 合法 ClassName() 报错
是否自动启用严格模式 ❌ 默认非严格 ✅ 强制启用 strict mode
方法是否可被枚举 ✅ prototype 方法可枚举 ❌ class 方法不可被枚举

尽管行为一致,语法糖依旧有其意义 —— 它使得面向对象编程更接近 Java 或 C#,也更具可维护性。

构造函数:不是结构体,但行为相似

我们常写的链表节点:

1
2
3
4
function ListNode(val, next) {
this.val = val;
this.next = next;
}

或者:

1
2
3
4
5
6
class LinkNode {
constructor(val, next) {
this.val = val;
this.next = next;
}
}

二者从功能上看是等价的:都定义了一个用于构造链表节点的工厂函数,并且都可通过 new 创建对象。

虽然 JS 没有“结构体”这个概念,但这些函数确实扮演了结构体构造器的角色:生成一组字段数据的对象,并可通过 prototype 共享方法。这种“结构体 + 方法”的组合体现了面向对象设计的基本思想。

函数也是对象吗?为什么可以被 new?

这部分常令人困惑,尤其是 JS 中「一切皆对象」的语言哲学。

1
2
3
function foo() {}
console.log(typeof foo); // "function"
console.log(foo instanceof Object); // true

在 JS 中,函数是一种特殊类型的对象,具有以下属性:

  • 可以拥有自己的属性(比如 foo.bar = 123)。
  • 可以作为构造函数通过 new 使用。
  • 可以被赋值、作为参数传递、返回等等。

new 一个函数时,会经历以下过程(简化版):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function MyConstructor(name) {
this.name = name;
// this 是函数执行时动态绑定的一个对象引用
}

const obj = new MyConstructor('Alice');
// new 做的事情就是:调用一个函数,并将其作为“构造函数”使用,它内部会创建一个新的对象并赋值给函数中的 this,以完成“构造”的含义
// 背后等价:
const obj = {}; // ① 创建一个空对象
obj.__proto__ = MyConstructor.prototype; // ② 设置它的原型指向构造函数的 prototype
MyConstructor.call(obj, 'Alice'); // ③ 调用构造函数,this绑定为 obj,name='Alice'
// 在把 MyConstructor 作为“构造函数”使用时,把 this 设置为 obj,并传入参数 'Alice'
// Function.prototype.call(thisArg, ...args) 是:
// 让你 “以某个对象为 this 执行函数”,这就是构造函数调用的关键
return obj; // ④ 返回 obj 这个对象(除非构造函数手动返回了别的对象)
// 在这个语句中,this 指向 obj,意味着构造函数内部的 this.name = name,实际上是 obj.name = 'Alice'

即使函数体中什么都不写,也可以用 new 调用并得到一个空对象:

1
2
function Empty() {}
const e = new Empty(); // e => {}

所以 new 并不依赖于函数体中是否使用了 this,但只有使用 this 才能对实例对象进行赋值初始化。

建议掌握的关键知识点总结

概念 说明
类的本质 构造函数 + 原型方法
构造函数与类的等价性 语法糖,但行为一致
new 的底层机制 创建对象 + 绑定原型 + 执行构造函数
函数是对象 可拥有属性,可被 new
类方法的挂载位置 挂载在 prototype 上
class 的语义变化 更严格的行为,不能提升等

结语

JS 的类看似简单,但背后连接的是函数、对象、原型链等多个核心机制。掌握这些机制,不只是为了写出链表这样的题,更重要的是在构建组件、封装逻辑、掌握框架(如 React 中类组件)时,能深入理解其行为。

不要满足于“可以写 class”,而要能回答:“它的本质是什么?”

Your browser is out-of-date!

Update your browser to view this website correctly.&npsb;Update my browser now

×