JavaScript中六种面试常考继承方式总结

作者:拾光

时间:2026-10-26

浏览:128

本文详细解析JavaScript中六种常见的继承模式,包括原型链、盗用构造函数、组合、原型式、寄生及寄生组合式继承。重点分析各模式的优缺点,如方法复用、参数传递及引用类型共享问题,并指出寄生组合式继承为最理想的引用类型继承范式,适合面试准备与深入理解。

文章目录

在JavaScript面试中,继承方式是一个高频考点。因此,深入理解各种继承模式的实现原理及其优缺点至关重要。

原型链继承

回顾原型与实例的关系:每个构造函数都拥有原型prototype,该原型对象的constructor属性指回构造函数本身。此外,实例内部包含一个指针__proto__,指向构造函数的prototype。若对这部分概念不熟悉,建议参考相关基础文章。以下通过代码示例进行说明。

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

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

function Child() {}

Child.prototype = new Parent();

var child1 = new Child();

console.log(child1.getName());

我们将构造函数Parent的实例赋值给构造函数Child的原型,从而实现了Child对Parent属性和方法的继承。

优点

1. 父类的方法得以复用。

缺点

1. 父类的所有属性被子类共享,若修改一个子类实例的引用类型属性,其他子类实例也会受到影响。

function Parent() {
  this.names = ["mick", "randy"];
}

function Child() {}

Child.prototype = new Parent();

var child1 = new Child();

child1.names.push("qr");

console.log(child1.names); // ["mick", "randy", "qr"]

var child2 = new Child();

console.log(child2.names); //  ["mick", "randy", "qr"]

2. 子类实例无法向父类构造函数传递参数。

盗用构造函数

盗用构造函数的核心思路是在子类构造函数内部,通过call或apply方法调用父类构造函数。

function Parent() {
  this.names = ["mick", "randy"];
}

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

var child1 = new Child();

child1.names.push("qr");

console.log(child1.names); // ["mick", "randy", "qr"]

var child2 = new Child();

console.log(child2.names); //  ["mick", "randy"]

在构造函数Child内部,通过call调用Parent。此时this即为构造函数Child,这意味着在创建Child实例时,都会对Parent进行初始化,相当于每个实例都拥有了names属性。

此外,这种方式允许向父构造函数传递参数。

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

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

var child1 = new Child("randy", 18);

console.log(child1.name); // randy

优点

  • 1. 可以在子类构造函数中向父类构造函数传递参数。
  • 2. 父类实例中的引用属性不会被共享。

缺点

1. 子类无法访问父类原型上的方法,因此所有方法和属性都必须定义在构造函数中,导致每次创建实例时都会重新初始化。

组合继承

组合继承结合了原型链继承和盗用构造函数继承的优点,并互补了二者的缺点。通过原型链继承可以访问父类原型上的属性和方法,而通过构造函数继承则可以访问父类实例的属性和方法。

function Parent(name) {
  this.name = name;
  this.colors = ["red", "blue", "green"];
}

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 child1 = new Child("mick", "18");

child1.colors.push("black");

console.log(child1.name); // mick
console.log(child1.age); // 18
console.log(child1.colors); // ["red", "blue", "green", "black"]

var child2 = new Child("randy", "20");

console.log(child2.name); // randy
console.log(child2.age); // 20
console.log(child2.colors); // ["red", "blue", "green"]

优点

  • 1. 父类的方法可以复用。
  • 2. 可以在子类构造函数中向父类传递参数。
  • 3. 父类构造函数中的引用属性不会共享。

缺点

1. 父类构造函数被调用了两次(如文中注释所示)。

原型式继承

其做法是创建一个临时构造函数,将传入的对象赋值给该构造函数的原型,然后返回该临时类型的一个实例。这实质上是对传入对象进行了一次浅复制。

function createObj(o) {
  function F() {}
  F.prototype = o;
  return new F();
}

这实际上是Object.create方法的模拟实现,即把传入的对象作为新对象的原型。

缺点:1. 引用类型的属性值始终会被共享,这一点与原型链继承相同。

function createObj(o) {
  function F() {}
  F.prototype = o;
  return new F();
}

let person = {
  name: "mick",
  colors: ["red", "blue", "green"],
};

let anotherPerson = createObj(person);
anotherPerson.name = "randy";
anotherPerson.colors.push("black");
console.log(anotherPerson.colors); // ['red', 'blue', 'green', 'black']
let yetAnotherPerson = createObj(person);

yetAnotherPerson.colors.push("yellow");
console.log(yetAnotherPerson.name); // mick
console.log(yetAnotherPerson.colors); // ['red', 'blue', 'green', 'black', 'yellow']

修改了anotherPerson.name的值,yetAnotherPerson.name并未发生变化。这是因为anotherPerson.name给anotherPerson添加了name的值,而非修改原型上的值。

原型链继承

寄生式继承

寄生式继承借鉴了寄生构造函数和工厂模式的思路:创建一个用于实现继承的函数,以某种方式增强对象,然后返回该对象。我们继续使用原型式继承创建的方法。

function createObj(o) {
  function F() {}
  F.prototype = o;
  return new F();
}

function createAnother(original) {
  let clone = createObj(original);
  clone.sayHi = function () {
    console.log("hi");
  };
  return clone;
}

缺点

与盗用构造函数类似,方法在每次创建对象时都会重新创建一遍。

寄生式组合继承

回顾组合继承的一个缺点:父类构造函数会被调用两次。如何优化这一缺点?

寄生式组合继承通过盗用构造函数继承属性,但使用混合式原型链继承方法。其基本思路是不通过调用父类构造函数给子类原型赋值,而是获取父类原型的一个副本。即使用寄生式继承来继承父类原型,然后将返回的新对象赋值给子类原型。

function Parent(name) {
  this.name = name;
  this.colors = ["red", "blue", "green"];
}

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

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

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

var child1 = new Child("mick", "18");

console.log(child1);

封装一下

function createObj(o) {
  function F() {}
  F.prototype = o;
  return new F();
}

function prototype(child, Parent) {
  var prototype = createObj(parent.prototype);
  prototype.constructor = child;
  child.prototype = prototype;
}
function Parent(name) {
  this.name = name;
  this.colors = ["red", "blue", "green"];
}

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

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

prototype(Child, Parent)

var child1 = new Child("mick", "18");

console.log(child1);

这种方式的高效性体现在它仅调用了一次Parent构造函数,从而避免了在Parent.prototype上创建不必要的、多余的属性。同时,原型链得以保持不变,因此可以正常使用instanceof和isPrototypeOf。开发人员普遍认为,寄生组合式继承是引用类型最理想的继承范式。

以上就是JavaScript中六种面试常考继承方式总结的详细内容,更多关于JavaScript继承方式的资料请关注本站其它相关文章!

实用工具推荐

电子包浆

绿化、压缩、噪点和虚拟水印,自由调节做旧程度。

使用 ›
图片压缩

选择画质和输出格式,预览压缩前后的大小。

使用 ›
相关文章