类与继承机制
ES6 引入的 class 关键字为 JavaScript 提供了更清晰的面向对象编程语法。虽然 class 本质上仍是原型继承的语法糖,但它大大提升了代码的可读性和可维护性。
Class 基础语法
声明与实例化
class Person {
// 构造函数 —— new 时自动调用
constructor(name, age) {
this.name = name;
this.age = age;
}
// 实例方法自动挂在原型上
greet() {
return `Hello, I'm ${this.name}`;
}
}
const alice = new Person('Alice', 28);
console.log(alice.greet()); // "Hello, I'm Alice"
console.log(alice instanceof Person); // true
constructor是可选的。如果不写,JavaScript 会提供一个空的默认构造函数。
与传统构造函数对比
// ES5 构造函数
function PersonES5(name, age) {
this.name = name;
this.age = age;
}
PersonES5.prototype.greet = function() {
return "Hello, I'm " + this.name;
};
// ES6 class 等价写法(语法糖)
class PersonES6 {
constructor(name, age) {
this.name = name;
this.age = age;
}
greet() {
return `Hello, I'm ${this.name}`;
}
}类表达式
类也可以用表达式形式定义,甚至可以匿名:
// 命名类表达式
const Rectangle = class Rect {
constructor(w, h) {
this.width = w;
this.height = h;
}
area() {
// 类名 Rect 仅在类内部可见
return this.width * this.height;
}
};
// 匿名类表达式
const Circle = class {
constructor(r) { this.radius = r; }
area() { return Math.PI * this.radius ** 2; }
};Getter 与 Setter
get 和 set 让你像访问属性一样调用方法:
class Temperature {
constructor(celsius) {
this._celsius = celsius; // 约定:下划线前缀表示"私有"
}
// 读取时调用
get fahrenheit() {
return this._celsius * 9 / 5 + 32;
}
// 赋值时调用
set fahrenheit(value) {
this._celsius = (value - 32) * 5 / 9;
}
get celsius() {
return this._celsius;
}
}
const temp = new Temperature(25);
console.log(temp.fahrenheit); // 77(像属性一样访问)
temp.fahrenheit = 86;
console.log(temp.celsius); // 30静态方法与属性
静态成员属于类本身,而非实例。常用于工具方法或工厂函数。
class MathUtils {
// 静态属性(ES2022)
static PI = 3.14159;
// 静态方法
static clamp(value, min, max) {
return Math.min(Math.max(value, min), max);
}
static randomInt(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
}
console.log(MathUtils.PI); // 3.14159
console.log(MathUtils.clamp(15, 0, 10)); // 10
// 静态方法中的 this 指向类本身
class Counter {
static count = 0;
static increment() {
// this 指向 Counter 类
this.count++;
return this.count;
}
}静态方法常用于替代构造函数,创建特定实例:
class DateRange {
constructor(start, end) {
this.start = start;
this.end = end;
}
// 工厂方法
static last7Days() {
const end = new Date();
const start = new Date();
start.setDate(end.getDate() - 6);
return new DateRange(start, end);
}
}
const range = DateRange.last7Days();私有字段(ES2022)
真正的私有成员使用 # 前缀声明,只能在类内部访问:
class BankAccount {
// 私有字段
#balance = 0;
#transactions = [];
constructor(owner) {
this.owner = owner;
}
deposit(amount) {
if (amount <= 0) throw new Error('金额必须大于 0');
this.#balance += amount;
this.#transactions.push({ type: 'deposit', amount });
return this.#balance;
}
withdraw(amount) {
if (amount > this.#balance) throw new Error('余额不足');
this.#balance -= amount;
this.#transactions.push({ type: 'withdraw', amount });
return this.#balance;
}
get balance() {
return this.#balance;
}
// 私有方法(ES2022)
#validate(amount) {
return typeof amount === 'number' && amount > 0;
}
}
const account = new BankAccount('Alice');
account.deposit(1000);
console.log(account.balance); // 1000
console.log(account.#balance); // SyntaxError:私有字段外部不可访问!私有字段的优势:
- 真正的封装,外部完全无法访问
- 不会与继承链上的同名属性冲突
- 不同于
_prefix命名约定,是语言层面的强制保护
继承与 super
extends 关键字
class Animal {
constructor(name) {
this.name = name;
}
speak() {
return `${this.name} makes a sound.`;
}
move() {
return `${this.name} is moving.`;
}
}
class Dog extends Animal {
constructor(name, breed) {
// 必须在使用 this 之前调用 super()
super(name);
this.breed = breed;
}
speak() {
// 调用父类方法
const base = super.speak();
return `${base} Woof! Woof!`;
}
}
const dog = new Dog('Buddy', 'Golden Retriever');
console.log(dog.speak()); // "Buddy makes a sound. Woof! Woof!"
console.log(dog.move()); // 继承自 Animal
console.log(dog instanceof Dog); // true
console.log(dog instanceof Animal); // true规则:子类构造函数中必须在使用
this之前调用super(),因为 ES6 类在继承时,子类的this是由父类构造函数创建的。
super 的两种用法
class Employee {
constructor(name, salary) {
this.name = name;
this.salary = salary;
}
getInfo() {
return `${this.name}: $${this.salary}`;
}
}
class Manager extends Employee {
constructor(name, salary, department) {
// 1. super() —— 调用父类构造函数
super(name, salary);
this.department = department;
}
getInfo() {
// 2. super.method() —— 调用父类原型上的方法
const base = super.getInfo();
return `${base}, Dept: ${this.department}`;
}
}继承内置类
ES6 class 可以继承内置类型如 Array、Error、Map:
class PowerArray extends Array {
// 判断数组是否为空
isEmpty() {
return this.length === 0;
}
// 返回平方后的新数组
squared() {
return this.map(x => x ** 2);
}
// 内置方法返回的类型会自动继承
static get [Symbol.species]() {
return Array;
}
}
const arr = new PowerArray(1, 2, 3, 4);
console.log(arr.isEmpty()); // false
console.log(arr.squared()); // [1, 4, 9, 16]
// filter 等方法返回 PowerArray(或 Symbol.species 指定的类型)
const filtered = arr.filter(x => x > 2);
console.log(filtered.isEmpty()); // true —— 如果 Symbol.species 返回 Array,则 false类字段与箭头函数
类字段语法允许直接在类体中定义属性,结合箭头函数可解决 this 绑定问题:
class Counter {
count = 0; // 类字段
// 箭头函数自动绑定 this
increment = () => {
this.count++;
console.log(this.count);
};
// 普通方法
decrement() {
this.count--;
}
}
const counter = new Counter();
const btn = { onclick: null };
// 箭头函数:this 永远指向实例
btn.onclick = counter.increment;
btn.onclick(); // 1 —— this 仍然是 counter
// 普通方法需要手动绑定
btn.onclick = counter.decrement.bind(counter);注意:箭头函数作为类字段会创建在实例上,而非原型上,每个实例都有独立的函数副本,略微增加内存开销。
私有字段在继承中的行为
class Base {
#secret = 'base secret';
getSecret() {
return this.#secret;
}
}
class Derived extends Base {
#secret = 'derived secret'; // 与父类的 #secret 完全不冲突!
reveal() {
// 访问的是 Derived 自己的 #secret
return this.#secret;
}
}
const d = new Derived();
console.log(d.getSecret()); // "base secret"
console.log(d.reveal()); // "derived secret"私有字段不是继承的——每个类有独立的私有命名空间。
小结
| 特性 | 语法 | 说明 |
|---|---|---|
| 构造函数 | constructor() {} | 实例化时执行 |
| 实例方法 | method() {} | 挂在原型上 |
| 静态方法 | static method() {} | 挂在类上 |
| Getter/Setter | get x() / set x(v) | 属性式访问 |
| 私有字段 | #field | 类内部私有 |
| 继承 | extends Parent | 建立原型链 |
| 调用父类 | super() / super.method() | 父类构造/方法 |
ES6 class 没有引入新的继承模型,它只是让 JavaScript 的原型继承变得更加直观和易于书写。理解 class 背后的原型链机制,是掌握 JavaScript 面向对象编程的关键。
评论
Written by
AI-Writer
Related Articles
数组内置方法综合运用
深入掌握 forEach、map、filter、reduce、find、sort 等数组方法的组合技巧,避开常见性能陷阱,写出更高效的函数式代码。
Read MoreJavaScript 对象与原型
深入理解 JavaScript 对象创建、属性描述符、原型链查找机制,以及原型链继承、构造函数继承和组合继承等多种实现方式
Read More手写二叉搜索树:插入、查找、删除与遍历全解析
从零实现二叉搜索树(BST),深入掌握插入、查找、删除、四种遍历算法的递归与非递归实现,并了解 AVL 树与红黑树的平衡思想。
Read More