JS继承的多种方式

177 阅读2分钟

1. 原型链继承

function Parent() {
  this.name = "parent";
  this.type = [1, 2];
}

function Son() {
  this.age = 100;
}

Son.prototype = new Parent();
const son1 = new Son();
const son2 = new Son();
console.log(son1 instanceof Son); // true
console.log(son1 instanceof Parent); // true

son1.type.push(3);
console.log(son1.type, son2.type);// [1, 2, 3] [1, 2, 3]

结论:原型链继承所构造的实例是既是父类的实例,又是子类的实例 缺点:实例会共用一个父类的引用对象。如上,修改了son1的type,son2的type也会相应变化。不能向Parent传参。

2. 借用构造函数继承

function Parent(type) {
  this.type = type || "defaultType"
  this.name = [1, 2];
}

function Son(type) {
  Parent.call(this);
}

const son1 = new Son("type1");
const son2 = new Son("type2");
console.log(son1 instanceof Son); // true
console.log(son1 instanceof Parent); // false

son1.name.push(3);
console.log(son1.name, son2.name); // [ 1, 2, 3 ] [ 1, 2 ]

结论:借用构造函数继承的实例是子类的实例,不是父类的实例。 优点:不会共用引用对象,可以向父类传参。 缺点:每次构造一个子类的实例都会调用父类的构造函数。

3. 组合继承(原型链&构造函数)

function Parent(type) {
  this.type = type || "defaultType";
  this.name = [1, 2]
}

function Son(type) {
  Parent.call(this, type); // 第一次
}

Son.prototype = new Son(); // 第二次
Son.prototype.constructor = Son;

const son = new Son();
console.log(son instanceof Son); // true
console.log(son instanceof Parent); // false

结论:组合继承所构造的实力是子类的实例,不是父类的实例。结合了原型链继承和构造函数继承的优点。 缺点:会调用两次父类构造函数

4. 原型继承

function createObj(o) {
  function F() {};
  F.prototype = o; // o => 类的实例
  return new F();
}

function Parent() {
  this.type = "parent";
  this.name = [1, 2];
}


const parent = new Parent();

const parent1 = createObj(parent);
const parent2 = createObj(parent);

parent1.name.push(3);

console.log(parent1.type, parent2.type); // parent parent
console.log(parent1.name, parent2.name); // [ 1, 2, 3 ] [ 1, 2, 3 ]

结论:createObj其实就是es5 Object.create()的实现,注意传进去的参数是一个实例。 缺点:通过createObj创建的实例会共用引用对象,跟原型链继承一样

5. 寄生式继承

function createObj(o) {
  const clone = Object.create(o);
  clone.say = function() {
    console.log('hi');
  }
  return clone;
}

缺点:每次创建实例都会调用一遍方法

6. 寄生组合式继承

上面所说的组合式继承的最大问题就是每次创建实例都会调用两次父类的构造函数,

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

function Son(type) {
  Parent.call(this);
}

const F = function() {};
F.prototype = new Parent();
Son.prototype = new F();;

// 如果直接Son.prototype = Parent.prototype;
// 修改Son.prototype.test = 123; 这样会影响Parent.prototype

结论:使用寄生组合式继承可以只调用一次父类的构造函数。如果是直接将父类的prototype赋值给子类的prototype,会出现如果子类的prototype有修改的话,影响到父类的prototype。 寄生组合继承集合了前面几种继承优点,几乎避免了上面继承方式的所有缺陷,是执行效率最高也是应用面最广的,就是实现的过程相对繁琐

7. 混入方式继承多个对象

function Animal() {
  this.type = "animal";
}

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

function Myclass() {
  Animal.call(this);
  Parent.call(this);
}

Myclass.prototype = Object.create(Animal.prototype);
Object.assign(Myclass.prototype, Parent.prototype);
Myclass.prototype.constuctor = Myclass;

const obj = new Myclass();
console.log(obj instanceof Myclass, obj instanceof Parent, obj instanceof Animal);// true false true

Object.assign会把Parent原型上的函数拷贝到MyClass上。

8. ES6类继承extends

相关资料参考

  1. JavaScript深入之继承的多种方式和优缺点
  2. Object.create
  3. JavaScript常用八种继承方案