ES6 Class 渐进式详解
ES6 引入的 class 是 JavaScript 面向对象编程的语法糖,它让原型继承的写法更清晰、更接近传统面向对象语言(如 Java、C++)的风格。本文将从基础到进阶,配合例子帮你彻底理解。
1. 为什么需要 ES6 Class?
在 ES6 之前,我们是用构造函数 + 原型链实现面向对象的,写法比较繁琐:
javascript
运行
js
// ES5 构造函数写法
function Person(name) {
this.name = name; // 实例属性
}
// 原型方法(共享给所有实例)
Person.prototype.sayHi = function() {
console.log(`你好,我是${this.name}`);
};
// 实例化
const alice = new Person('Alice');
alice.sayHi(); // 输出:你好,我是Alice
ES6 的 class 把这些逻辑封装得更简洁,本质上还是基于原型链,但可读性大幅提升。
2. 基本语法:定义一个类
用 class 关键字定义类,类名通常首字母大写:
javascript
运行
js
// 定义 Person 类
class Person {
// 类的主体
}
// 实例化(和构造函数一样,用 new)
const alice = new Person();
这是最简单的类,但还没有属性和方法,接下来我们逐步添加。
3. 构造函数:constructor
constructor 是类的默认方法 ,当你 new 一个实例时,会自动调用它,用来初始化实例属性:
javascript
运行
js
class Person {
// 构造函数,接收参数
constructor(name, age) {
// this 指向当前实例
this.name = name; // 实例属性:姓名
this.age = age; // 实例属性:年龄
}
}
// 实例化时传参
const alice = new Person('Alice', 25);
console.log(alice.name); // 输出:Alice
console.log(alice.age); // 输出:25
- 如果不写
constructor,类会自动生成一个空的constructor()。 this关键字在类中指向当前实例对象。
4. 实例方法:类的行为
在类中定义方法,不需要 function 关键字,直接写方法名即可。这些方法会被添加到类的原型上,所有实例共享:
javascript
运行
js
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
// 实例方法:打招呼
sayHi() {
console.log(`你好,我是${this.name},今年${this.age}岁`);
}
// 实例方法:过生日
birthday() {
this.age++; // 修改实例属性
console.log(`${this.name}过生日了,现在${this.age}岁`);
}
}
const alice = new Person('Alice', 25);
alice.sayHi(); // 输出:你好,我是Alice,今年25岁
alice.birthday(); // 输出:Alice过生日了,现在26岁
5. 静态成员:类本身的属性 / 方法
用 static 关键字定义静态属性 或静态方法 ,它们属于类本身 ,而不是实例,需要通过类名调用:
javascript
运行
js
class Person {
// 静态属性(ES2022 支持,旧版本需用 Person.type = '人类' 定义)
static type = '人类';
constructor(name) {
this.name = name;
}
// 静态方法:判断是否是 Person 的实例
static isPerson(obj) {
return obj instanceof Person;
}
}
// 调用静态成员(通过类名)
console.log(Person.type); // 输出:人类
const alice = new Person('Alice');
console.log(Person.isPerson(alice)); // 输出:true
// 错误:实例无法调用静态成员
// alice.type; // undefined
// alice.isPerson(); // 报错
- 静态方法中的
this指向类本身,而不是实例。
6. 继承:extends 和 super
用 extends 关键字让一个类继承另一个类,子类会拥有父类的所有属性和方法,还可以扩展自己的内容:
javascript
运行
js
// 父类(基类)
class Person {
constructor(name) {
this.name = name;
}
sayHi() {
console.log(`你好,我是${this.name}`);
}
}
// 子类(派生类):Student 继承 Person
class Student extends Person {
constructor(name, studentId) {
// 必须先调用 super(),它会执行父类的 constructor
super(name);
this.studentId = studentId; // 子类自己的属性
}
// 子类自己的方法
study() {
console.log(`${this.name}(学号:${this.studentId})正在学习`);
}
// 重写父类方法(覆盖)
sayHi() {
// 可以通过 super 调用父类的方法
super.sayHi();
console.log(`我是一名学生,学号是${this.studentId}`);
}
}
// 实例化子类
const bob = new Student('Bob', '2024001');
bob.sayHi(); // 输出:你好,我是Bob → 我是一名学生,学号是2024001
bob.study(); // 输出:Bob(学号:2024001)正在学习
关键点:
- 子类的
constructor必须先调用super(),否则会报错(因为子类的this是基于父类构建的)。 - 子类可以重写 父类的方法(如
sayHi),也可以通过super.方法名()调用父类的原方法。
7. Getter 和 Setter:控制属性访问
用 get 和 set 关键字定义属性访问器,可以在读取或设置属性时添加逻辑(如验证、计算):
javascript
运行
js
class Person {
constructor(name) {
this._name = name; // 用下划线表示"私有属性"(约定俗成,不是真私有)
}
// getter:读取 name 属性时触发
get name() {
return this._name.toUpperCase(); // 自动转大写
}
// setter:设置 name 属性时触发
set name(value) {
if (value.length < 2) {
console.log('名字太短了!');
return;
}
this._name = value;
}
}
const alice = new Person('Alice');
console.log(alice.name); // 输出:ALICE(触发 getter)
alice.name = 'Bob'; // 触发 setter
console.log(alice.name); // 输出:BOB
alice.name = 'A'; // 输出:名字太短了!(setter 验证失败)
- 注意:
get和set是方法,但调用时不需要加括号,像普通属性一样访问即可。 - 这里的
_name是 "伪私有",外部依然可以直接访问alice._name(ES2022 支持真私有属性#name,但兼容性需注意)。
8. 类的本质:语法糖
虽然 class 看起来像新东西,但本质上还是函数 + 原型链,我们可以验证一下:
javascript
运行
js
class Person {}
// 1. 类的类型是 function
console.log(typeof Person); // 输出:function
// 2. 类有 prototype 属性(和构造函数一样)
console.log(Person.prototype); // 输出:{ constructor: Person }
// 3. 实例的 __proto__ 指向类的 prototype
const alice = new Person();
console.log(alice.__proto__ === Person.prototype); // 输出:true
所以 class 只是让原型继承的写法更优雅,底层逻辑和 ES5 是一致的。
总结
class是 ES6 的语法糖,简化了面向对象编程。constructor用于初始化实例属性,new时自动调用。- 实例方法定义在原型上,静态方法用
static定义,属于类本身。 - 继承用
extends,子类constructor必须先调用super()。 get/set用于控制属性的读写逻辑。