源本科技 | 码上会

JavaScript 中的继承

2026/01/13
24
0

学习目标

  • 理解 JavaScript 中继承的基本概念及其在面向对象编程中的作用

  • 掌握基于原型的继承实现方式

  • 熟悉 ES6 class 语法下的类继承机制

  • 了解 Mixin、Object.create()Object.setPrototypeOf() 和工厂函数等不同继承模式的使用场景与优缺点


什么是继承

inheritance-660x454.webp

在面向对象编程(OOP)中,继承允许一个类(或对象)从另一个类(或对象)获取属性和方法。这种机制避免了重复代码,提高了代码复用性和可维护性。

在 JavaScript 中,继承主要通过 原型链(Prototype Chain) 实现。所有对象都通过其内部的 [[Prototype]] 链接指向另一个对象,从而可以访问父对象的属性和方法。

类比:就像孩子继承父母的特征一样,子对象可以“继承”父对象的行为和状态。


基于原型的继承

这是 JavaScript 最原始、最核心的继承方式,适用于构造函数风格的编程。

function Animal(name) {
    this.name = name;
}

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

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

// 设置原型链:Dog 继承 Animal 的方法
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog; // 修复构造函数指向

// 为 Dog 添加特有方法
Dog.prototype.bark = function () {
    console.log(`${this.name} barks: Woof!`);
};

const myDog = new Dog("Buddy");
myDog.speak(); // Buddy makes a sound.
myDog.bark();  // Buddy barks: Woof!

关键点说明:

  • Animal.call(this, name):确保 Dog 实例拥有 name 属性。

  • Object.create(Animal.prototype):建立原型链,使 Dog.prototype 能访问 Animal.prototype 上的方法。

  • 修复 constructor:避免 myDog.constructor 指向 Animal

这是 ES5 时代最标准的手动继承写法。


ES6 类继承

ES6 引入了 class 语法糖,使继承更直观、更接近传统 OOP 语言。

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

    speak() {
        return `My name is ${this.name}`;
    }
}

class Dog extends Animal {
    constructor(name) {
        super(name); // 调用父类构造函数
    }
}

const dog = new Dog('Pranjal');
console.log(dog.speak()); // My name is Pranjal

特点:

  • 使用 extends 关键字声明继承关系。

  • 子类构造函数中必须调用 super() 才能使用 this

  • 自动建立原型链,无需手动设置 prototype

  • 语法简洁,可读性强,是现代 JavaScript 推荐的继承方式。


Mixin 模式

多源继承

JavaScript 不支持多重继承,但可通过 Mixin 模拟从多个来源“混合”方法。

const Speakable = {
    speak() {
        return `${this.name} speaks`;
    }
};

const Walkable = {
    walk() {
        return `${this.name} walks`;
    }
};

function Person(name) {
    this.name = name;
}

// 将多个对象的方法合并到 Person.prototype
Object.assign(Person.prototype, Speakable, Walkable);

const person = new Person('Pranjal');
console.log(person.speak()); // Pranjal speaks
console.log(person.walk());  // Pranjal walks

适用场景:

  • 需要从多个独立模块复用行为(如日志、验证、序列化等)。

  • 避免深层次继承带来的耦合问题。

注意:Mixin 是浅拷贝,若方法有冲突,后合并的会覆盖前者。


使用 Object.create()

Object.create(proto) 创建一个新对象,其原型为 proto,常用于对象字面量之间的继承。

const animal = {
    name: 'Unknown',
    age: 0,
    introduce() {
        return `My name is ${this.name}`;
    }
};

const dog = Object.create(animal);
dog.name = 'Buddy';

console.log(dog.age);        // 0(继承自 animal)
console.log(dog.introduce()); // My name is Buddy(this 指向 dog)

优势:

  • 无需构造函数,直接基于现有对象创建新对象。

  • 适合轻量级、动态的对象扩展。


Object.setPrototypeOf()

动态设置原型

该方法可在运行时修改对象的原型链。

const behavior = {
    name: 'Pranjal'
};

const speaker = {
    speak() {
        return `${this.name} speaks`;
    }
};

Object.setPrototypeOf(speaker, behavior);
console.log(speaker.speak()); // Pranjal speaks

注意事项:

  • 虽然灵活,但性能较差,且破坏引擎优化。

  • 官方文档建议避免使用,仅在必要时用于调试或特殊场景。


工厂函数实现

工厂函数返回配置好的对象,虽不涉及原型链,但可实现类似继承的效果。

function createPerson(name) {
    return {
        name: name,
        greet() {
            return `Hello, my name is ${this.name}`;
        }
    };
}

const alice = createPerson('Alice');
const bob = createPerson('Bob');

console.log(alice.greet()); // Hello, my name is Alice
console.log(bob.greet());   // Hello, my name is Bob

特点:

  • 每个对象独立拥有方法(无共享原型),内存开销较大。

  • 适合简单对象创建,无需复杂继承结构。

  • 隐藏实现细节,提供清晰的创建接口。


各种继承方式对比

方式

是否使用原型链

支持多继承

内存效率

可读性

适用场景

原型继承(ES5)

兼容旧环境

ES6 类继承

现代项目首选

Mixin

✅(通过合并)

✅(模拟)

多行为复用

Object.create()

对象模板继承

setPrototypeOf

调试 / 动态修改

工厂函数

✅(组合)

简单对象创建


重点总结

  • JavaScript 的继承本质是基于原型链的,所有继承方式最终都依赖于此机制。

  • ES6 class + extends 是当前最推荐的继承写法,语义清晰且兼容性良好。

  • Mixin 模式可弥补 JavaScript 不支持多重继承的不足,适合功能组合。

  • 避免滥用 Object.setPrototypeOf(),它会影响性能并降低代码可预测性。

  • 工厂函数虽非真正继承,但在某些场景下更简洁、更安全。


思考题

  1. 在基于原型的继承中,为什么需要手动修复 Dog.prototype.constructor = Dog?如果不修复会有什么影响?

  2. ES6 的 class 是否真的引入了“类”?它与传统面向对象语言(如 Java)中的类有何本质区别?

  3. 如果你正在开发一个需要同时具备“可飞行”、“可游泳”和“可奔跑”能力的角色系统,你会选择哪种继承方式?为什么?