js的继承

360 阅读3分钟

1.原型链继承

核心:

将父类的实例作为子类的原型。 优点:

  • 实现简单。 缺点:
  • 包含引用类型值的原型属性会被所有实例共享,这会导致对一个实例的修改会影响另一个实例。
  • 要想为子类新增属性和方法,必须在new Cat()这样的语句后执行,不能放在构造器中 如:在创建 Child 的实例时,不能向Cat传参。如果要加,只能在new Cat()以后加 由于这两个问题的存在,实践中很少单独使用原型链。
// 原型链继承
function SuperType() {
  this.name = 'zhangsan';
}
SuperType.prototype.sayName = function() {
  console.log(this.name);
}
function SubType() {}
// 父类的实例作为子类的原型
SubType.prototype = new SuperType();

// 测试
let instance1 = new SubType();
instance1.sayName();

2.借用构造函数继承

核心:

用.call()或.apply()将父类构造函数引入子类函数,等于是复制父类的实例属性给子类。 优点:

  • 1.只继承了父类构造函数的属性,没有继承父类原型的属性。
  • 2.解决了原型链继承的两个缺点。
  • 3.可以继承多个构造函数属性(call多个)
  • 4.在子实例中可向父实例传参。 缺点:
  • 1.只能继承父类构造函数的属性。
  • 2.无法实现构造函数的复用。(每次用都需要重新调用)
  • 3.每个新实例都有父类构造函数的副本,臃肿。所以借用构造函数继承很少单独使用。
// 借用构造函数继承
function SuperType(name) {
  this.name = name;
}
function SubType(name,age) {
  SuperType.call(this,name);
  this.age = age;
}

// 测试
let person1 = new SubType('zhangsan',22);
console.log(person1.name);
console.log(person1.age);

3.组合继承

核心:

通过构造函数实现对实例属性的继承,通过原型链实现对方法的继承。 优点:

  • 1.可以继承父类原型上的属性,可以传参,可以复用。
  • 2.每个新实例引入的构造函数属性是私有的。 缺点:
  • 调用了两次(call一次,new一次)父类构造函数(耗内存),子类的构造函数会代替原型上的那个父类构造函数。
// 组合继承
// 父类
function SuperType(name) {
  this.name = name;
  this.colors = ['blue','red'];
}
// 父类方法
SuperType.prototype.sayName = function() {
  console.log(this.name);
}
// 子类
function SubType(name,age) {
  // 继承属性 构造继承
  SuperType.call(this,name);
  this.age = age;
}
// 继承方法 原型继承
SubType.prototype = new SuperType()
// 子类方法
SubType.prototype.sayAge = function() {
  console.log(this.age);
}

// 测试
let person1 = new SubType('zhangsan',22);
person1.sayName();
person1.sayAge();
person1.colors.push('black');
console.log(person1.colors);

4.原型式继承

核心:

ES5 Object.create的模拟实现,将传入的对象作为创建的对象的原型。 优点:

  • 参考原型链继承 缺点:
  • 参考原型链继承
// 原型式继承
// 先封装一个函数容器,用来输出对象和承载继承的原型
function createObj(o) {
  function F() {}
  F.prototype = o; // 继承了传入的参数
  return new F(); // 返回函数对象
}

// 测试
let person = {
  name: 'zhangsan',
  friends: ['lisi','wangwu']
}
let person1 = createObj(person);
let person2 = createObj(person);
person1.name = 'person1';
console.log(person2.name);
person1.friends.push('zhou');
console.log(person2.friends);

5.寄生式继承

核心:

就是给原型式继承外面套了个壳子。 优点:

  • 没有创建自定义类型,因为只是套了个壳返回对象,这个函数顺理成章就成了创建的新对象。 缺点:
  • 跟借用构造函数模型一样,每次创建对象都会创建一遍方法。
  • 没用到原型,无法复用。
// 寄生式继承
// 在原型式的外面套了个函数
function createObj(o) {
  // 这一步可以看做是原型式继承的简写
  let clone = Object.create(o);
  // 新增属性
  clone.sayName = function() {
    console.log('hi');
  }
  return clone; // 返回这个对象
}

// 测试
function child() {}
let child1 = createObj(child)
child1.sayName()

6.寄生组合式继承

核心:

使用寄生式继承来继承父类原型,然后将返回的新对象赋值给子类原型,寄生式组合继承可以算是引用类型继承的最佳模式。

// 寄生组合式继承
// 父类
function SuperType(name) {
  this.name = name;
  this.colors = ['blue','red'];
}
SuperType.prototype.sayName = function() {
  console.log(this.name);
}
// 子类
function.SubType(name,age) {
  SuperType.call(this,name);
  this.age = age;
}
// 封装一个继承函数
function extend(subtype,supertype) {
  let prototype = Object(supertype.prototype);
  prototype.constructor = subtype;
  subtype.prototype = prototype;
}
// 调用
extend(SubType,SuperType)

// 测试
SubType.prototype.sayAge = function() {
  console.log(this.age);
}
let person1 = new SubType('zhangsan',22);
person1.sayName();
person1.sayAge();
person1.colors.push('black');
console.log(person1.colors);