使用 JavaScript 创建类有多种方式,早期主要依靠构造函数配合原型对象,ES6 之后新增的 class 关键字让代码结构更加清晰。class 本质上并非一套全新的对象模型,而是对构造函数和原型链写法的封装。理解这一点,后续处理继承、this 绑定和私有状态时会更有方向。

一、从构造函数到 class:语法糖背后的原型机制
在 ES6 之前,创建一个“类”通常需要定义一个构造函数,然后把公共方法挂到 prototype 对象上。这种写法虽然可行,但代码分散,手动管理原型链也容易出错。class 关键字把构造过程、实例方法和静态成员集中到一个代码块中,阅读成本更低。
传统构造函数的写法如下:
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.sayHello = function() {
console.log('Hello, I am ' + this.name);
};
const p = new Person('张三', 28);
p.sayHello();
使用 class 改造后,constructor 方法承担构造函数职责,其他方法自动成为原型方法。下面的示例实现同样的功能:
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
sayHello() {
console.log('Hello, I am ' + this.name);
}
}
const p = new Person('张三', 28);
p.sayHello();
两种写法产生的结果基本一致:Person.prototype.sayHello 都会成为可被实例调用的原型方法。区别在于 class 声明不会提升,必须先在代码中定义再使用;而函数声明则会提升。此外,class 内部默认运行在严格模式下,方法内部的 this 不会意外指向全局对象。
需要注意的是,class 并不是一种新的数据类型,typeof Person 返回的值仍然是 function。这说明 class 本质上是函数的一种特殊写法,只是语法更加贴近传统面向对象语言。
二、extends 继承与 super 的执行顺序
继承是类的重要特性。通过 extends 关键字,子类可以继承父类的实例方法和静态方法,还可以在自己的构造函数中扩展属性。当子类定义了 constructor 时,第一行必须调用 super(...),之后才能使用 this。这一限制是为了保证父类构造函数先完成初始化,避免子类操作尚未创建的实例。如果不显式定义 constructor,子类会自动调用 super(...args) 并传入所有参数。
下面的代码展示了继承的基本用法,同时演示了方法覆写与通过 super.method() 调用父类实现:
class Animal {
constructor(name) {
this.name = name;
}
speak() {
console.log(this.name + ' makes a sound.');
}
static info() {
return 'Animal class';
}
}
class Dog extends Animal {
constructor(name, breed) {
super(name);
this.breed = breed;
}
speak() {
super.speak();
console.log(this.name + ' barks.');
}
}
const dog = new Dog('旺财', '金毛');
dog.speak();
console.log(Dog.info());
运行这段代码会先输出“旺财 makes a sound.”,再输出“旺财 barks.”,最后打印“Animal class”。可以看到 super.speak() 成功调用了父类原型上的方法,而静态方法 info 也被子类继承。继承链上静态方法同样可用,这是很多开发者容易忽略的细节。
还有一个容易混淆的点:super 作为函数调用时返回的是子类实例,并且会初始化父类构造函数;作为对象访问时则指向父类的原型对象。两者虽然都用 super 表达,但语义完全不同。如果在子类 constructor 中先使用 this 再调用 super,会直接抛出 ReferenceError,这类错误在调试时比较常见。
三、静态成员、私有字段与常见使用误区
静态成员通过 static 关键字定义,只能由类本身访问,实例无法调用。静态方法常用于编写工具函数,例如日期格式化、数学计算等。静态方法内部的 this 指向类本身而不是实例,因此不要试图在静态方法中访问实例属性。
class DateHelper {
static formatDate(date) {
const year = date.getFullYear();
const month = date.getMonth() + 1;
return year + '-' + month + '-' + date.getDate();
}
}
console.log(DateHelper.formatDate(new Date()));
从 ES2022 开始,私有字段使用 # 前缀声明。私有字段只能在类内部访问,外部访问会抛出语法错误。这为封装内部状态提供了原生能力,不再需要借助闭包或 WeakMap 等技巧。下面是一个计数器的例子:
class Counter {
#count = 0;
increment() {
this.#count++;
}
get value() {
return this.#count;
}
}
const counter = new Counter();
counter.increment();
console.log(counter.value);
console.log(counter.#count);
示例最后一行 counter.#count 在类外部访问会直接报 SyntaxError,这体现了私有字段的强封装性。私有字段与普通属性不同,不能通过 this['#count'] 或 this.#count 之外的方式访问,而且它不会被继承到子类实例的原型链上。
除了语法本身,还有一些实践中频繁出现的误区:一是把 class 当作函数直接调用,比如 Person('张三'),这在严格模式下会报错;二是忘记在子类 constructor 中调用 super;三是在普通函数中提取实例方法导致 this 丢失;四是误以为 class 会改变 JavaScript 的原型继承模型。理解 class 背后的原型机制,才能真正用好这些语法糖,避免隐藏的坑。
JavaScript类ES6 class继承修改时间:2026-10-05 04:25:42