原型链继承的实现与缺陷

什么是原型链继承

在JavaScript中,原型链继承是实现对象间继承关系的主要方式。每个JavaScript对象都有一个内部链接指向另一个对象(称为它的原型),而这个原型对象又有自己的原型,依此类推,直到某个对象的原型为null为止。这种链式结构被称为原型链。

javascript 复制代码
function Parent() {
  this.name = 'parent';
}

Parent.prototype.sayName = function() {
  console.log(this.name);
};

function Child() {
  this.childName = 'child';
}

// 实现原型链继承
Child.prototype = new Parent();

const child = new Child();
child.sayName(); // 输出: parent

原型链继承的实现原理

  1. 构造函数、原型和实例的关系

    • 每个构造函数都有一个原型对象(prototype)
    • 原型对象包含一个指向构造函数的指针(constructor)
    • 实例包含一个指向原型对象的内部指针(__proto__)
  2. 继承的实现

    • 通过将子类的原型对象指向父类的实例来实现继承
    • 这样就在子类和父类之间建立了原型链
  3. 查找机制

    • 当访问实例的属性/方法时,首先在实例自身查找
    • 如果找不到,则沿着原型链向上查找
    • 直到找到该属性/方法或到达原型链末端(null)

原型链继承的缺陷

  1. 引用类型值的共享问题
javascript 复制代码
function Parent() {
  this.colors = ['red', 'blue'];
}

function Child() {}

Child.prototype = new Parent();

const child1 = new Child();
child1.colors.push('green');

const child2 = new Child();
console.log(child2.colors); // ['red', 'blue', 'green']

所有子类实例共享同一个父类实例的属性,当属性是引用类型时,一个实例修改会影响所有实例。

  1. 无法向父类构造函数传参

在创建子类实例时,无法向父类构造函数传递参数,因为父类实例化是在设置子类原型时完成的。

  1. 原型链断裂问题
javascript 复制代码
Child.prototype = new Parent();
// 需要手动修复constructor指向
Child.prototype.constructor = Child;

如果不手动修复,子类实例的constructor会指向父类构造函数。

  1. 性能问题
    • 每次创建子类实例时都会执行一次父类构造函数
    • 原型链过长会导致属性查找时间增加

改进方案

由于原型链继承存在上述缺陷,实践中通常会结合其他继承方式:

  1. 借用构造函数继承(经典继承)

    • 在子类构造函数中调用父类构造函数
    • 解决了引用类型共享和传参问题
    • 但无法继承父类原型上的方法
  2. 组合继承

    • 结合原型链继承和借用构造函数继承
    • 既能在实例上拥有独立属性,又能共享方法
javascript 复制代码
function Parent(name) {
  this.name = name;
  this.colors = ['red', 'blue'];
}

Parent.prototype.sayName = function() {
  console.log(this.name);
};

function Child(name, age) {
  Parent.call(this, name); // 第二次调用Parent
  this.age = age;
}

Child.prototype = new Parent(); // 第一次调用Parent
Child.prototype.constructor = Child;
Child.prototype.sayAge = function() {
  console.log(this.age);
};
  1. 寄生组合式继承
    • 目前最理想的继承方式
    • 只调用一次父类构造函数
    • 保持原型链不变
javascript 复制代码
function inheritPrototype(child, parent) {
  const prototype = Object.create(parent.prototype);
  prototype.constructor = child;
  child.prototype = prototype;
}

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

function Child(name, age) {
  Parent.call(this, name);
  this.age = age;
}

inheritPrototype(Child, Parent);

总结

原型链继承是JavaScript中最基本的继承方式,它通过建立对象间的原型链来实现属性和方法的共享。虽然简单直观,但也存在引用类型共享、无法传参等明显缺陷。在实际开发中,通常会采用组合继承或更优的寄生组合式继承来规避这些问题。理解原型链继承的原理和缺陷,有助于我们更好地掌握JavaScript面向对象编程的核心机制。