六大继承方案总结(继承系列-3)

前端印记

共 4966字,需浏览 10分钟

 ·

2021-10-12 11:56

目录:

  • 原型链继承

    • 问题:

  • 借用构造函数(不算标准的继承模式)

  • 组合继承(伪经典继承)

    • 核心思想:

    • 优点

    • 问题:(寄生组合式继承就是为了解决这一问题)

  • 原型式继承

    • 实现原理:

    • Object.create()

    • 优点

    • 缺点

  • 寄生式继承

    • 原理

    • 缺点

  • 寄生组合式继承

    • 实现思路

    • 核心逻辑

    • 优点

    • 圣杯模式

原型链继承和借用构造函数继承在前边的篇幅都说过,本篇关于这两个方法只是简单过一遍。详情请看前传。


原型链继承

function SuperType({
 this.property = true;
}
SuperType.prototype.getSuperValue = function ({
 return this.property;
};
function SubType({
 this.subproperty = false;
}
//继承了 SuperType 
SubType.prototype = new SuperType();
SubType.prototype.getSubValue = function ({
 return this.subproperty;
};
var instance = new SubType();
alert(instance.getSuperValue()); //true

问题:

最主要的问题来自包含引用类型值的原型。我们前面介绍过包含引用类型值的原型属性会被所有实例共享;而这也正是为什么要在构造函数中,而不是在原型对象中定义属性的原因。在通过原型来实现继承时,原型实际上会变成另一个类型的实例。于是,原先的实例属性也就顺理成章地变成了现在的原型属性了。

借用构造函数(不算标准的继承模式)

又叫“对象伪装”或“经典继承”

function SuperType(name{
 this.name = name;
 this.colors = ["red""blue""green"];
}
function SubType({
 // 继承了 SuperType 
 SuperType.call(this'同时传递参数');
}
var instance1 = new SubType();
instance1.colors.push("black");
console.log(instance1.colors); // "red,blue,green,black" 
var instance2 = new SubType();
console.log(instance2.colors); // "red,blue,green"
console.log(instance2.name); // '同时传递参数'

解决了“引用类型值的原型属性会被所有实例共享”的问题。

组合继承(伪经典继承)

综合了原型链和盗用构造函数,将两者的优点集中了起来。

核心思想:

使用借用构造函数的技术实现“实例属性”和方法的继承,使用原型链实现“原型属性”和方法的继承。

function SuperType(name{
 // 定义实例属性
  this.name = name; 
 this.colors = ["red""blue""green"];
}
// 定义原型链方法
SuperType.prototype.sayName = function ({
 console.log(this.name);
};
function SubType(name, age{
 SuperType.call(this, name); // (第一次调用构造函数)利用“借用构造函数”的思想,继承了SuperType实例的name,colors属性
  
  // 自定义自己的实例属性
 this.age = age;
}
SubType.prototype = new SuperType(); // (第二次调用构造函数)利用“构造原型链”的思想,继承原型属性和方法
SubType.prototype.constructor = SubType;
SubType.prototype.sayAge = function ({
 console.log(this.age);
};
var instance1 = new SubType("Nicholas"29);
instance1.colors.push("black");
console.log(instance1.colors); //"red,blue,green,black"
instance1.sayName(); //"Nicholas";
instance1.sayAge(); //29

var instance2 = new SubType("Greg"27);
console.log(instance2.colors); //"red,blue,green"
instance2.sayName(); //"Greg";
instance2.sayAge(); //27

优点

组合继承弥补了原型链和盗用构造函数的不足,是 JavaScript 中使用最多的继承模式。而且组合继 承也保留了 instanceof 操作符和 isPrototypeOf()方法识别合成对象的能力。

问题:(寄生组合式继承就是为了解决这一问题)

但依旧拥有原型继承和借用构造函数的缺点:

  1. 组合继承最大的问题就是效率问题,因为无论在什么情况下,都会调用两次构造函数:
    • 一次是在创建子类型原型时,
    • 另一次是在子类型构造函数内部。
  2. 第二个问题是会继承多余的属性。
    上例中,使用SubType.prototype = new SuperType();,SubType将会继承SuperType实例对象的name、colors属性,虽然name没有被创建。因此造成了一个问题,SubType.prototype上会存在不必要的,多余的属性。如name,colors属性同时存在于SubType.prototype和SubType中。

原型式继承

实现原理:

创建一个临时构造函数,将传入的对象赋值给这个构造函数的原型,然后返回这个临时类型的一个实例。

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

使用

let person = {
  name"Nicholas",
  friends: ["Shelby""Court""Van"]
};
let anotherPerson = object(person);
anotherPerson.name = "Greg";
anotherPerson.friends.push("Rob");
let yetAnotherPerson = object(person);
yetAnotherPerson.name = "Linda";
yetAnotherPerson.friends.push("Barbie");
console.log(person.friends); // "Shelby,Court,Van,Rob,Barbie"

Object.create()

规范化了原型式继承的概念,用于实现原型式继承

let person = {
  name"Nicholas",
  friends: ["Shelby""Court""Van"]
};
let anotherPerson = Object.create(person);
anotherPerson.name = "Greg";
anotherPerson.friends.push("Rob");
let yetAnotherPerson = Object.create(person);
yetAnotherPerson.name = "Linda";
yetAnotherPerson.friends.push("Barbie");
console.log(person.friends); // "Shelby,Court,Van,Rob,Barbie"

优点

不需要单独创建构造函数

缺点

属性中包含的引用值始终会在相关对象间共享

寄生式继承

与原型式继承比较接近,相当于把原型式继承又包装了一遍。

原理

思路类似于寄生构造函数和工厂模式:创建一个实现继承的函数,以某种方式增强对象,然后返回这个对象。

基本的寄生继承模式如下:

function createAnother(original{
  let clone = object(original); // 通过调用函数创建一个新对象 
  clone.sayHi = function (// 以某种方式增强这个对象
    console.log("hi");
  };
  return clone; // 返回这个对象 
}

适合主要关注对象,而不在乎类型和构造函数的场景

缺点

通过寄生式继承给对象添加函数会导致函数难以重用(比如上述代码的sayHi)

寄生组合式继承

实现思路

寄生式组合继承通过盗用构造函数继承属性,但使用混合式原型链继承方法。

基本思路是不通过调用父类构造函数给子类原型赋值,而是取得父类原型的一个副本。说到底就是使用寄生式继承来继承父类原型,然后将返回的新对象赋值给子类原型

概括:

  1. 通过借用构造函数来继承属性;
  2. 通过原型链来继承方法(圣杯式继承)。

核心逻辑

function object(o{
  function F({}
  F.prototype = o;
  return new F();
}
function inheritPrototype(subType, superType{
  let prototype = object(superType.prototype); // 创建对象 
  prototype.constructor = subType; // 增强对象 
  subType.prototype = prototype; // 赋值对象
}

这个函数接收两个参数: 子类构造函数和父类构造函数。

函数内部,第一步是创建父类原型的一个副本。

给返回的 prototype 对象设置 constructor 属性,为了解决由于重写原型导致默认 constructor 丢失的问题。

将新创建的对象赋值给子类型的原型。

优点

只调用了一次构造函数,避免了子类原型上不必要也用不到的属性,因此可以说这个例子的效率更高。
而且,原型链仍然保持不变,因此 instanceof 操作符和 isPrototypeOf()方法正常有效。
寄生式组合继承可以算是引用类型继承的最佳模式。

圣杯模式

最后发现,这不就是圣杯模式的写法么!

function inherit(Target,Origin){
 function F(){};
 F.prototype = Origin.prototype;
 Target.prototype = new F();
 Target.prototype.constuctor = Target;
 target.prototype.uber = Origin.prototype;
}

用圣杯式原型链继承方法替代原来的new 构造原型链的方法,这样儿子身上就没有父亲身上的实例化属性了!good!




愿你历尽千帆,归来仍是少年。


让我们一起携手同走前端路!

关注公众号回复【加群】即可

● 工作中常见页面布局的n种实现方法

● 三栏响应式布局(左右固宽中间自适应)的5种方法

● 两栏自适应布局的n种实现方法汇总

● 工作中常见的两栏布局案例及分析

● 垂直居中布局的一百种实现方式

● 常用九宫格布局的几大方法汇总

● 为什么操作DOM会影响WEB应用的性能?

● 移动端滚动穿透的6种解决方案

● Vue + TypeScript 踩坑总结


浏览 38
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报