JavaScript类的基本定义与实例化方法
在 JavaScript 开发中,有一条常见的建议是:当需要创建多个具有相同结构和行为的对象时,优先使用 class 关键字而不是工厂函数或对象字面量。这条建议的核心在于利用类的语法结构来明确“模板”与“实例”的界限,使代码更具可读性和维护性。然而,这并非铁律,理解其背后的机制以及何时不适用同样重要。
1. 类是对象的蓝图
类本身不是对象,而是创建对象的模板。定义一个类就像绘制一张建筑图纸,而实例化则是根据图纸建造具体的房屋。这种分离让开发者可以专注于定义一次逻辑,然后重复生成数据独立的实体。
使用 class 关键字定义类时,必须包含一个名为 constructor 的特殊方法。这个方法在每次使用 new 操作符创建实例时自动执行,用于初始化实例的状态。
class User {
constructor(name, role) {
this.name = name;
this.role = role;
}
}
在这段代码中,constructor 接收参数并将其赋值给 this。这里的 this 指向即将被创建的新实例。如果不定义 constructor,JavaScript 引擎会默认提供一个空的构造函数。

类作为蓝图生成多个独立实例的过程
2. new 操作符的执行机制
实例化的过程由 new 操作符触发。它不仅仅是一个关键字,背后涉及四个关键步骤:创建一个新对象、将新对象的原型链接到类的原型、执行构造函数并将 this 绑定到新对象、返回新对象(除非构造函数显式返回其他对象)。
const admin = new User('Alice', 'admin');
console.log(admin.name); // 输出: Alice
console.log(admin instanceof User); // 输出: true
如果忘记使用 new 直接调用类,JavaScript 会抛出 TypeError。这与普通函数不同,普通函数忘记 new 可能只会导致 this 指向全局对象或 undefined(严格模式下),而类强制要求使用 new,从而避免了隐式错误。
3. 实例方法与原型链
在类中定义的方法默认挂载在类的 prototype 上,而不是每个实例上。这意味着所有实例共享同一份方法代码,节省了内存空间。
class User {
constructor(name) {
this.name = name;
}
greet() {
return `Hello, I am ${this.name}`;
}
}
const user1 = new User('Bob');
const user2 = new User('Charlie');
console.log(user1.greet === user2.greet); // 输出: true
上述代码中,user1.greet 和 user2.greet 引用的是同一个函数。这种共享机制是原型继承的核心优势。如果在 constructor 内部定义方法,则每个实例都会拥有独立的方法副本,通常不推荐这样做,除非需要闭包捕获特定状态。

实例通过原型链共享同一份方法代码
4. 静态方法与工具函数
有些行为不属于某个具体实例,而是属于类本身。例如,创建一个默认用户或验证输入格式。这时可以使用 static 关键字定义静态方法。
class User {
static createGuest() {
return new User('Guest', 'guest');
}
}
const guest = User.createGuest();
静态方法不能通过实例调用,只能通过类名调用。它们常用于工厂模式或工具函数,将相关逻辑封装在类的命名空间下,避免污染全局作用域。
5. getter 与 setter 的控制
直接暴露属性可能导致数据不一致。通过 get 和 set 关键字,可以在访问或修改属性时插入逻辑,实现数据验证或计算属性。
class Product {
constructor(price) {
this._price = price;
}
get price() {
return this._price;
}
set price(value) {
if (value < 0) {
throw new Error('Price cannot be negative');
}
this._price = value;
}
}
const item = new Product(100);
item.price = -10; // 抛出错误
这种机制让外部调用者像访问普通属性一样使用 item.price,但内部却执行了验证逻辑,保持了接口的简洁性与数据的安全性。

通过getter和setter拦截属性访问与修改
6. 例外:类不是函数提升
虽然 class 在底层仍然是函数,但它不具备函数声明的提升特性。函数声明可以在定义之前调用,而类必须先定义后使用。这是因为类存在“暂时性死区”(Temporal Dead Zone)。
// 正确
class Circle {}
const c = new Circle();
// 错误
const s = new Square(); // ReferenceError
class Square {}
这一例外要求开发者注意代码的组织顺序,或者使用模块系统来管理依赖。此外,类内部默认启用严格模式,这意味着不能在类中使用未声明的变量,这也与普通非严格模式下的函数行为不同。
7. 例外:私有字段的支持差异
传统 JavaScript 没有真正的私有属性,开发者常使用下划线前缀 _property 作为约定。ES2022 引入了以 # 开头的私有字段语法,提供了语言级别的隐私保护。
class BankAccount {
#balance = 0;
deposit(amount) {
this.#balance += amount;
}
getBalance() {
return this.#balance;
}
}
然而,这一特性在较旧的浏览器或环境中可能需要转译。如果目标环境不支持,仍需依赖闭包或 WeakMap 来模拟私有性。这是选择类语法时需要考虑的兼容性例外。

使用#符号定义真正的私有字段
8. 何时不使用类
尽管类很有用,但并非所有场景都适合。如果只需要一个简单的数据容器,对象字面量更轻量;如果需要灵活的闭包状态,工厂函数可能更合适。类适合那些具有明确身份、生命周期和共享行为的实体。
判断原则是:如果多个对象共享行为且需要明确的类型标识,使用类;如果只是数据的临时聚合,使用对象;如果需要复杂的闭包隔离,使用工厂函数。理解这些边界,才能在实际项目中做出恰当的选择。
小结而言,JavaScript 的类提供了结构化的对象创建方式,通过 constructor 初始化状态,通过 new 创建实例,通过原型共享方法。但需注意其无提升特性及环境兼容性问题,根据实际需求灵活选用。
免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。









