您的位置:

JavaScript 继承详解

一、原型链继承

原型链继承是 JavaScript 中最基本的继承方式。在原型链继承中,子类对象的原型指向其父类对象,通过这样的方式实现了继承。

代码示例:

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

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

function Child() {}
Child.prototype = new Parent();

var child = new Child();
child.getName(); // "parent"

实现原理:当访问 child.getName() 时,JavaScript 引擎会先查找 child 对象身上是否有此方法,没有则去原型链中的 Parent.prototype 上查找,找到了则执行,否则继续向上查找,直到找到 Object.prototype。

优点:代码简单易懂,易于实现。

缺点:原型链继承会将父类的引用类型属性共享给所有子类实例,容易造成意外修改,同时无法向父类构造函数传递参数。

二、借用构造函数继承(经典继承)

借用构造函数继承是在子类构造函数内部调用父类构造函数,并通过 call 或 apply 方法将父类实例绑定到子类实例上,从而实现继承。

代码示例:

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

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

var child = new Child("child");
console.log(child.name); // "child"

实现原理:通过在子类构造函数内部调用父类构造函数,并绑定 this,从而实现了继承。借用构造函数继承避免了父类引用类型属性被共享的问题。

优点:避免了引用类型属性被共享的问题,同时可以向父类构造函数传递参数。

缺点:每次都需要调用父类构造函数,无法复用父类原型对象上的方法,导致内存浪费,并且子类实例无法访问父类原型上的属性和方法。

三、组合继承(原型链继承和经典继承的组合)

组合继承是将原型链继承和借用构造函数继承结合使用的方式。通过借用构造函数继承父类的实例属性,通过原型链继承父类的原型属性和方法,从而实现继承。

代码示例:

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

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

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

Child.prototype = new Parent();
Child.prototype.constructor = Child;

var child = new Child("child", 18);
child.getName(); // "child"

实现原理:通过在子类构造函数内部通过 call 或 apply 方法调用父类构造函数,从而实现继承父类实例属性。然后将子类原型指向父类的实例,从而实现继承父类原型属性及方法。

优点:既继承了父类构造函数属性,又继承了父类原型上的属性及方法。

缺点:会调用两次父类构造函数,造成内存浪费。

四、原型式继承

原型式继承通过利用一个空对象作为中介实现对对象的复制,从而实现继承。

代码示例:

const parent = {
  name: "parent",
  getName: function () {
    console.log(this.name);
  }
};

const child = Object.create(parent);
child.name = "child";
child.getName(); // "child"

实现原理:通过 Object.create() 方法以一个对象作为参数,创建一个继承自该对象的新对象。

优点:可以实现对某个对象进行简单继承,避免了构造函数和原型的复杂度。

缺点:父类引用类型属性共享给所有子类实例,容易造成意外修改,无法向父类构造函数传递参数。

五、寄生式继承

寄生式继承是在原型式继承的基础上进行了封装,利用一个函数返回一个以父类对象为基础的新对象,实现继承。

代码示例:

const parent = {
  name: "parent",
  getName: function () {
    console.log(this.name);
  }
};

function createChild(parent, name) {
  const child = Object.create(parent);
  child.name = name;
  return child;
}

const child = createChild(parent, "child");
child.getName(); // "child"

实现原理:借助工厂模式的思想,通过一个函数返回一个对象,从而实现继承。

优点:在不需要耦合构造函数的情况下实现继承。

缺点:与构造函数、原型链继承等方式一样,父类引用类型属性共享给所有子类实例,容易造成意外修改,无法向父类构造函数传递参数。

六、寄生组合式继承(最优解)

寄生组合式继承是在组合继承的基础上进行改良,通过寄生式继承来继承父类的原型对象,从而避免了组合继承中调用两次父类构造函数的问题。

代码示例:

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

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

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

Child.prototype = Object.create(Parent.prototype);
Child.prototype.constructor = Child;

var child = new Child("child", 18);
child.getName(); // "child"

实现原理:通过 Object.create() 方法以父类原型对象作为参数,创建一个继承自该对象的新对象,从而避免了调用两次父类构造函数的问题。

优点:避免了调用两次父类构造函数的问题,既继承了父类构造函数属性,又继承了父类原型上的属性及方法。

缺点:代码复杂度较高。