返回首页

说说你对原型链继承和类继承的理解?

问题解析

JavaScript 中的继承经历了从原型链继承到 ES6 Class 继承的演变。理解这两种继承方式的区别和联系,对于掌握 JavaScript 的面向对象编程至关重要。ES6 Class 本质上是原型链继承的语法糖,但提供了更清晰、更易于理解的语法。

核心概念

原型链继承

原型链继承是 JavaScript 传统的继承方式,基于构造函数和原型对象实现。每个构造函数都有一个 prototype 属性,实例通过 __proto__ 链接到原型对象,形成原型链。

类继承(ES6 Class)

ES6 引入的 Class 语法提供了一种更简洁、更接近传统面向对象语言的继承写法,但本质上仍然是基于原型链的。

详细解答

一、原型链继承详解

1. 基本实现

// 父类构造函数
function Animal(name) {
    this.name = name;
    this.colors = ['black', 'white'];
}

// 父类原型方法
Animal.prototype.sayName = function() {
    console.log(`My name is ${this.name}`);
};

// 子类构造函数
function Dog(name, breed) {
    // 借用构造函数继承实例属性
    Animal.call(this, name);
    this.breed = breed;
}

// 建立原型链继承(关键步骤)
Dog.prototype = Object.create(Animal.prototype);

// 修正 constructor 指向
Dog.prototype.constructor = Dog;

// 子类原型方法
Dog.prototype.bark = function() {
    console.log('Woof! Woof!');
};

// 使用
const dog = new Dog('Buddy', 'Golden Retriever');
dog.sayName();  // "My name is Buddy"
dog.bark();     // "Woof! Woof!"

console.log(dog instanceof Dog);     // true
console.log(dog instanceof Animal);  // true

2. 原型链继承的演进

// 方式1:原型链继承(不推荐,引用类型共享)
function Sub1() {}
Sub1.prototype = new Parent();

// 方式2:借用构造函数(不推荐,无法继承原型方法)
function Sub2() {
    Parent.call(this);
}

// 方式3:组合继承(推荐,但调用两次父类构造函数)
function Sub3() {
    Parent.call(this);  // 第二次调用
}
Sub3.prototype = new Parent();  // 第一次调用

// 方式4:寄生组合式继承(最佳实践)
function inherit(Child, Parent) {
    Child.prototype = Object.create(Parent.prototype);
    Child.prototype.constructor = Child;
}

function Sub4() {
    Parent.call(this);
}
inherit(Sub4, Parent);

二、类继承详解(ES6)

1. 基本语法

// 父类
class Animal {
    // 构造函数
    constructor(name) {
        this.name = name;
        this.colors = ['black', 'white'];
    }

    // 实例方法
    sayName() {
        console.log(`My name is ${this.name}`);
    }

    // 静态方法
    static isAnimal(obj) {
        return obj instanceof Animal;
    }
}

// 子类
class Dog extends Animal {
    constructor(name, breed) {
        // 调用父类构造函数,必须在使用 this 之前
        super(name);
        this.breed = breed;
    }

    // 子类方法
    bark() {
        console.log('Woof! Woof!');
    }

    // 重写父类方法
    sayName() {
        // 调用父类方法
        super.sayName();
        console.log(`I am a ${this.breed}`);
    }

    // 子类静态方法
    static isDog(obj) {
        return obj instanceof Dog;
    }
}

// 使用
const dog = new Dog('Buddy', 'Golden Retriever');
dog.sayName();
// "My name is Buddy"
// "I am a Golden Retriever"
dog.bark();  // "Woof! Woof!"

console.log(Dog.isDog(dog));      // true
console.log(Animal.isAnimal(dog)); // true

2. 类的 getter 和 setter

class Person {
    constructor(firstName, lastName) {
        this.firstName = firstName;
        this.lastName = lastName;
    }

    // getter
    get fullName() {
        return `${this.firstName} ${this.lastName}`;
    }

    // setter
    set fullName(name) {
        [this.firstName, this.lastName] = name.split(' ');
    }
}

const person = new Person('John', 'Doe');
console.log(person.fullName);  // "John Doe"

person.fullName = 'Jane Smith';
console.log(person.firstName);  // "Jane"
console.log(person.lastName);   // "Smith"

3. 类的私有字段和方法

class BankAccount {
    // 私有字段(ES2022)
    #balance = 0;

    constructor(initialBalance) {
        this.#balance = initialBalance;
    }

    // 私有方法
    #validateAmount(amount) {
        return amount > 0;
    }

    deposit(amount) {
        if (this.#validateAmount(amount)) {
            this.#balance += amount;
            return true;
        }
        return false;
    }

    withdraw(amount) {
        if (this.#validateAmount(amount) && amount <= this.#balance) {
            this.#balance -= amount;
            return true;
        }
        return false;
    }

    getBalance() {
        return this.#balance;
    }
}

const account = new BankAccount(1000);
account.deposit(500);
console.log(account.getBalance());  // 1500

// 无法访问私有成员
console.log(account.#balance);  // SyntaxError

深入理解

原型链继承 vs 类继承对比

// ========== 原型链继承 ==========
function Animal1(name) {
    this.name = name;
}

Animal1.prototype.speak = function() {
    console.log(`${this.name} makes a sound`);
};

function Dog1(name, breed) {
    Animal1.call(this, name);
    this.breed = breed;
}

Dog1.prototype = Object.create(Animal1.prototype);
Dog1.prototype.constructor = Dog1;

Dog1.prototype.bark = function() {
    console.log(`${this.name} barks`);
};

// ========== 类继承 ==========
class Animal2 {
    constructor(name) {
        this.name = name;
    }

    speak() {
        console.log(`${this.name} makes a sound`);
    }
}

class Dog2 extends Animal2 {
    constructor(name, breed) {
        super(name);
        this.breed = breed;
    }

    bark() {
        console.log(`${this.name} barks`);
    }
}

// ========== 本质相同 ==========
const dog1 = new Dog1('Buddy', 'Golden');
const dog2 = new Dog2('Buddy', 'Golden');

console.log(dog1.__proto__ === Dog1.prototype);  // true
console.log(dog2.__proto__ === Dog2.prototype);  // true

console.log(Dog1.prototype.__proto__ === Animal1.prototype);  // true
console.log(Dog2.prototype.__proto__ === Animal2.prototype);  // true

ES6 Class 的本质

class Person {
    constructor(name) {
        this.name = name;
    }

    sayHello() {
        console.log(`Hello, I'm ${this.name}`);
    }

    static createAnonymous() {
        return new Person('Anonymous');
    }
}

// 本质上等同于:
function PersonES5(name) {
    this.name = name;
}

PersonES5.prototype.sayHello = function() {
    console.log(`Hello, I'm ${this.name}`);
};

PersonES5.createAnonymous = function() {
    return new PersonES5('Anonymous');
};

// 验证
console.log(typeof Person);           // "function"
console.log(Person.prototype);        // {constructor: ƒ, sayHello: ƒ}
console.log(Person === Person.prototype.constructor);  // true

继承中的 super

class Parent {
    constructor(value) {
        this.value = value;
    }

    static staticMethod() {
        return 'Parent static';
    }

    instanceMethod() {
        return 'Parent instance';
    }
}

class Child extends Parent {
    constructor(value, extra) {
        // super 作为函数调用:调用父类构造函数
        super(value);
        this.extra = extra;
    }

    instanceMethod() {
        // super 作为对象使用:访问父类原型
        return `${super.instanceMethod()} + Child`;
    }

    static staticMethod() {
        // super 访问父类静态方法
        return `${super.staticMethod()} + Child`;
    }
}

const child = new Child('value', 'extra');
console.log(child.instanceMethod());      // "Parent instance + Child"
console.log(Child.staticMethod());        // "Parent static + Child"

最佳实践

1. 优先使用 ES6 Class

// ✅ 推荐:使用 Class 语法
class Animal {
    constructor(name) {
        this.name = name;
    }

    speak() {
        console.log(`${this.name} makes a sound`);
    }
}

class Dog extends Animal {
    constructor(name, breed) {
        super(name);
        this.breed = breed;
    }

    speak() {
        super.speak();
        console.log(`${this.name} barks`);
    }
}

2. 正确处理 constructor

class Parent {
    constructor(name) {
        this.name = name;
    }
}

class Child extends Parent {
    constructor(name, age) {
        // ✅ 必须先调用 super()
        super(name);
        // ❌ 在 super() 之前使用 this 会报错
        this.age = age;
    }
}

// 如果子类不需要额外构造逻辑,可以省略 constructor
class SimpleChild extends Parent {
    // 自动继承父类 constructor
}

3. 使用私有字段保护数据

class Counter {
    #count = 0;  // 私有字段

    increment() {
        this.#count++;
        return this;
    }

    decrement() {
        this.#count--;
        return this;
    }

    getCount() {
        return this.#count;
    }
}

// 方法链式调用
const counter = new Counter();
counter.increment().increment().decrement();
console.log(counter.getCount());  // 1

4. 抽象基类模式

// 抽象基类(不能被直接实例化)
class Animal {
    constructor(name) {
        if (new.target === Animal) {
            throw new Error('Animal 是抽象类,不能直接实例化');
        }
        this.name = name;
    }

    // 抽象方法(子类必须实现)
    speak() {
        throw new Error('子类必须实现 speak 方法');
    }
}

class Dog extends Animal {
    speak() {
        console.log(`${this.name} says Woof!`);
    }
}

// const animal = new Animal('Test');  // Error
const dog = new Dog('Buddy');
dog.speak();  // "Buddy says Woof!"

面试要点

  1. 原型链继承和 Class 继承的区别?

    • 语法层面:Class 更清晰、更接近传统面向对象语言
    • 本质层面:Class 是原型链继承的语法糖,底层实现相同
    • 功能层面:Class 提供了 super、static、私有字段等特性
  2. ES6 Class 的本质是什么?

    • Class 本质上还是函数
    • Class 的 prototype 属性与构造函数相同
    • extends 本质上是建立原型链
  3. super 的作用?

    • 在 constructor 中:调用父类构造函数
    • 在实例方法中:访问父类原型上的方法
    • 在静态方法中:访问父类的静态方法
    • 必须在子类 constructor 中使用 this 之前调用 super()
  4. 如何实现多重继承?

    • JavaScript 不支持多重继承
    • 可以通过 Mixin 模式实现类似效果
    • 使用组合优于继承
  5. Class 中的私有字段是如何实现的?

    • ES2022 引入 # 前缀语法
    • 私有字段只能在类内部访问
    • 是语言层面的私有,不是约定
  6. 什么时候使用原型链继承而不是 Class?

    • 需要兼容非常旧的浏览器时
    • 需要更灵活地操作原型时
    • 大多数情况下推荐使用 Class