javascript

类与继承机制

By AI-Writer 15 min read

ES6 引入的 class 关键字为 JavaScript 提供了更清晰的面向对象编程语法。虽然 class 本质上仍是原型继承的语法糖,但它大大提升了代码的可读性和可维护性。

Class 基础语法

声明与实例化

javascript
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 会提供一个空的默认构造函数。

与传统构造函数对比

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}`;
  }
}

类表达式

类也可以用表达式形式定义,甚至可以匿名:

javascript
// 命名类表达式
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

getset 让你像访问属性一样调用方法:

javascript
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

静态方法与属性

静态成员属于类本身,而非实例。常用于工具方法或工厂函数。

javascript
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;
  }
}

静态方法常用于替代构造函数,创建特定实例:

javascript
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)

真正的私有成员使用 # 前缀声明,只能在类内部访问:

javascript
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 关键字

javascript
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 的两种用法

javascript
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 可以继承内置类型如 ArrayErrorMap

javascript
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 绑定问题:

javascript
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);

注意:箭头函数作为类字段会创建在实例上,而非原型上,每个实例都有独立的函数副本,略微增加内存开销。

私有字段在继承中的行为

javascript
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/Setterget x() / set x(v)属性式访问
私有字段#field类内部私有
继承extends Parent建立原型链
调用父类super() / super.method()父类构造/方法

ES6 class 没有引入新的继承模型,它只是让 JavaScript 的原型继承变得更加直观和易于书写。理解 class 背后的原型链机制,是掌握 JavaScript 面向对象编程的关键。

#javascript #es6 #class #继承 #面向对象

评论

A

Written by

AI-Writer

Related Articles

javascript
#11

数组内置方法综合运用

深入掌握 forEach、map、filter、reduce、find、sort 等数组方法的组合技巧,避开常见性能陷阱,写出更高效的函数式代码。

Read More
javascript
#4

JavaScript 对象与原型

深入理解 JavaScript 对象创建、属性描述符、原型链查找机制,以及原型链继承、构造函数继承和组合继承等多种实现方式

Read More