一、class是什么?
在 JavaScript 中,class 是 ES6(ECMAScript 2015)引入的语法糖(syntactic sugar),本质上是基于 JavaScript 原型继承(Prototype Inheritance) 的一种更清晰、更接近传统面向对象语言的写法。它的底层实现仍然是原型和构造函数,但语法更加简洁易读。
类比:
想象类就像一张 “饼干模具”,模具决定了饼干的形状(属性)和图案(方法),用这个模具压出来的饼干(对象)都长得一样。
二、class 的本质?
class 的本质
-
class不是一种新的类型,而是对原有原型继承机制的封装。 -
它通过更直观的语法定义构造函数和原型方法,例如:
// ES5 传统写法(构造函数 + 原型) function Person(name) { this.name = name; } Person.prototype.sayHello = function() { console.log(`Hello, I'm ${this.name}`); }; // ES6 class 写法 class Person { constructor(name) { this.name = name; } sayHello() { console.log(`Hello, I'm ${this.name}`); } }两者完全等价,
class只是语法上的改进。
三、class 的特点
- 构造函数:通过
constructor方法定义。 - 方法定义:类方法直接写在类内部,自动绑定到原型(
prototype)。 - 不可枚举:类方法默认不可枚举(而 ES5 原型方法默认可枚举)。
- 必须用
new调用:直接调用类会报错(而传统构造函数不强制new)。 - 继承语法:通过
extends和super简化继承(比 ES5 的Object.create和手动绑定原型更清晰)。
四、为什么要用类?
- 代码复用:避免重复写相同的代码。
- 结构清晰:将数据(属性)和操作(方法)封装在一起。
- 继承:子类可以继承父类的特性,扩展功能。
五、class的知识点
1. 基本结构
class Animal {
// 属性:
// 用于初始化对象的属性。
// 在创建对象时自动调用。
constructor(name) {
this.name = name; // this 指向当前对象
}
// 方法:
// 定义在类中的函数,供对象调用
speak() {
console.log(`${this.name} makes a noise.`);
}
}
// 创建对象(实例化):
// 用 `new` 关键字创建对象
const dog = new Animal("Dog");
dog.speak(); // "Dog makes a noise."
2. 继承(extends 和 super)
可以继承父类的属性和方法,并扩展自己的功能。
-
extends关键字// 父类 class Animal { constructor(name) { this.name = name; } eat() { console.log(`${this.name}在吃饭`); } } // 子类继承父类 class Dog extends Animal { bark() { console.log("汪汪!"); } } // 使用 const myDog = new Dog("阿黄"); myDog.eat(); // 输出:阿黄在吃饭(继承自父类) myDog.bark(); // 输出:汪汪!(子类自己的方法) -
super关键字(1) 子类构造函数中调用父类构造函数
如果子类有自己的构造函数(
constructor),必须在构造函数内 第一行 调用super(),否则会报错。
这是因为子类需要通过super初始化父类的属性和方法,才能正确构建对象。class Animal { constructor(name) { this.name = name; } } class Dog extends Animal { constructor(name, breed) { super(name); // 必须先调用父类构造函数!不然会报错(必须在访问 this 前调用 super()) this.breed = breed; } } const myDog = new Dog("阿黄", "金毛"); console.log(myDog.name); // "阿黄"(来自父类) console.log(myDog.breed); // "金毛"(来自子类)(2) 子类方法中调用父类方法
当子类需要 扩展(而非完全覆盖)父类方法 时,可以先用
super.method()调用父类方法,再添加子类特有的逻辑。class Animal { speak() { console.log("动物发出声音"); } } class Dog extends Animal { speak() { super.speak(); // 先调用父类的 speak() console.log("汪汪!"); // 再添加子类逻辑 } } const dog = new Dog(); dog.speak(); // 输出: // 动物发出声音 // 汪汪!(3)调用父类的静态方法
如果父类有静态方法,子类可以通过
super调用它们。class Animal { static info() { return "这是动物类"; } } class Dog extends Animal { static info() { return super.info() + " -> 这是子类狗"; } } console.log(Dog.info()); // 输出: // "这是动物类 -> 这是子类狗"总结:何时必须使用
super?
| 场景 | 用法 | 目的 |
|---|---|---|
| 子类构造函数中 | super(参数) | 初始化父类属性,确保子类正确继承 |
| 子类方法中扩展父类方法 | super.方法名(参数) | 复用父类逻辑,避免重复代码 |
| 子类调用父类静态方法 | super.静态方法名(参数) | 复用父类静态方法逻辑 |
3. 静态方法 static
- 属于类本身的方法,不能通过实例调用。
- 常用于工具函数:
class MathUtils {
static add(a, b) {
return a + b;
}
}
console.log(MathUtils.add(2, 3)); // 5(直接通过类调用)
4. 私有字段 #
- 以
#开头的属性是私有的,只能在类内部访问:
class BankAccount {
#balance = 0; // 私有属性
deposit(amount) {
this.#balance += amount;
}
getBalance() {
return this.#balance;
}
}
const account = new BankAccount();
account.deposit(100);
console.log(account.getBalance()); // 100
// account.#balance; // 报错!
5. Getter 和 Setter
控制属性的读取和修改:
class Person {
constructor(name) {
this._name = name; // 使用下划线约定表示“内部属性”
}
// Getter:读取 name 时触发
get name() {
return this._name.toUpperCase(); // 返回大写的名字
}
// Setter:修改 name 时触发
set name(newName) {
this._name = newName.trim(); // 自动去除首尾空格
}
}
// 使用示例
const p = new Person(" 小明 ");
console.log(p.name); // 输出 "小明"(自动调用 getter)
p.name = " 小红 "; // 自动调用 setter
console.log(p.name); // 输出 "小红"(setter 处理后)
其他问题解答
Q1:如果父类没有构造函数,子类需要写 super() 吗?
需要!即使父类没有显式定义 constructor,JavaScript 会隐式生成一个空的构造函数。子类仍需调用 super()。
class Parent {} // 隐式有 constructor() {}
class Child extends Parent {
constructor() {
super(); // 必须调用!
}
}
Q2:能否在异步函数或回调中使用 super?
不能!super 的调用必须在构造函数或方法内部直接执行,且必须在访问 this 之前调用。
Q3:为什么 super 必须在构造函数的第一行调用?
JavaScript 要求子类必须先通过 super 初始化父类,才能访问 this(确保继承链正确建立)。
Q4: class与它的传统原型有什么区别?
| 特性 | ES5 构造函数 | ES6 class |
|---|---|---|
| 方法可枚举性 | 默认可枚举 | 默认不可枚举 |
| 调用方式 | 可不加 new(可能出错) | 必须加 new |
| 继承语法 | 手动操作原型链(复杂) | extends 和 super(简洁) |
| 代码结构 | 分散(构造函数 + 原型方法) | 集中(类内部统一) |