原型链继承的终极实现(继承系列-2)

前端印记

共 5925字,需浏览 12分钟

 ·

2021-10-12 11:55

继承发展史

  1. 传统形式 -> 原型链
    缺点:过多的继承了没用的属性,原型上所有的东西都被继承了,效率上很不好。
  2. 借用构造函数
    缺点:
    • 只能使用构造函数的方法,不能继承所借用的构造函数的原型;
    • 每次构造函数都要多执行一个函数
  3. 共享原型
    缺点:不能随便改动自己的原型
  4. 终极方案:圣杯模式

继承的几种形式实现


  • 一、原型链继承:

    • 实现原理:

    • 缺点:

  • 二、借用构造函数(“对象伪装”或“经典继承”)

    • 实现原理:

    • 优点:

    • 缺点:

  • 三、公有原型(共用原型)

    • 实现原理:

    • 代码封装:

    • 缺点:

  • 四、圣杯模式(优化第三种方式)

    • 原理:

    • 封装:

    • 不完美

    • 圣杯模式终极封装(实现继承通俗写法,拿去面试吧)

    • 圣杯模式终极封装 - 高性能写法


一、原型链继承:

最基础、最简单的继承。

实现原理:

将子类的原型指向父类的实例化对象

// 祖宗
Grand.prototype.lastName = 'guo';
function Grand(){
 this.name = 'unknow'
}
var grand = new Grand();
// 父亲
Father.prototype = grand; // 核心:爸爸的原型指向爷爷的实例化对象
function Father(){
 this.name = 'xy'
}
var father = new Father();
// 儿子
Son.prototype = father;  // 儿子的原型指向爸爸的实例化对象
function Son(){
 this.name = 'jf'
}
var son = new Son();
console.log(son.lastName)

缺点:

  1. 过多的继承了没用的属性
  2. 原型中包含的引用值会在所有实例间共享
  3. 不能向父类型的构造函数中传递参数
    上边例子中,son继承了father的所有属性和grand的所有属性,但实际上它可能只需要lastName

二、借用构造函数(“对象伪装”或“经典继承”)

严格来说这种模式不能算是继承,因为只是借用别人原型上的东西。

实现原理:

在子类构造函数中使用“父类.call(指向子类实例化对象的this)”的方式调用父类构造函数。

function Person(name,age){
 this.name = name;
 this.age = age;
}
function Student(name, age, grade, sex){
 Person.call(this, name, age); // 核心
 this.grade = grade;
 this.sex = sex;
}
var student = new Student('gjf',14,6,'female');

从工业化的层面上的角度来说,借用别人的方法就可以被叫做继承了。但是没用继承构造函数Person的原型,只是用了Person的方法。

优点:

就是可以在子类构造函数中向父类构造函数传参

缺点:

  • 每次都要执行Person这个函数一遍,每构造一个对象,都要至少执行两个方法。浪费效率
  • 另外,子类也不能访问父类原型上定义的方法。所以这种方式很少单独使用。

不过在现有开发上,遇到这种多个人可以公用一个方法的情况,还是推荐借用构造函数的模式。

三、公有原型(共用原型)

现在用的标准模式。

实现原理:

将子类的原型直接指向父类的原型

Father.prototype.lastName = 'guo';
function Father(){}
function Son(){}

按照之前的方法是将Son的原型指向“new Father()”出来的实例对象,以借此来继承Father的原型prototype。

现在这种方法简单粗暴,直接将Son的原型指向Father的原型即可:

Son.prototype = Father.prototype; // inherit(Son,Father);
var son = new Son();

至此,son和father共用同一个原型:Father.prototype

代码封装:

从根本上让构造函数继承,而非实例对象。

function inherit(Target,Origin){
 Target.prototype = Origin.prototype;
}

缺点:

属性公用:
因为Son.prototype已经指向了Father的prototype,他俩共同指向同一个堆内存空间的对象,导致Son.prototype上添加属性,Father的prototype也会增加。

四、圣杯模式(优化第三种方式)

原理:

在第三种方案的思想基础上,再寻找一个新的中间人,让中间人的原型指向需要继承的构造函数的原型,后边想要继承的子孙的原型,等于中间人的实例对象即可。(第一种原型链继承和第三种公有原型继承的结合)

Father.prototype.lastName = 'guo';
function Father(){
}
function Son(){
}
// 关键点:创建中间人F,并将其原型指向父类的原型,而子类的原型指向F的实例化对象。
function F(){}
F.prototype = Father.prototype // 公有原型继承
Son.prototype = new F();// 原型链继承

中间人就是构造函数 F,
让他的原型直接指向父级构造函数的原型
后边有想要继承Father的子孙们,只需要将子孙的原型指向中间人F用new构造出来的实例对象就行了。

封装:

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

不完美

但是这个写法还不够完美,系统自定义的constructor还缺少:

圣杯模式终极封装(实现继承通俗写法,拿去面试吧)

function inherit(Target,Origin){
 function F(){};
 F.prototype = Origin.prototype;
 // 注意这个顺序,一定要在prototype指向以后再构造新的F():
 Target.prototype = new F();
 // 解决构造器指向紊乱
 Target.prototype.constuctor = Target;
 // 希望最后构造出来的对象能找到自己的超类super,也就是最终祖宗Father
 target.prototype.uber = Origin.prototype; // super是关键字,使用uber代替
}

圣杯模式终极封装 - 高性能写法

JQ中的利用闭包的高性能写法:(雅虎军规里的高级写法)
F是用来过渡的,没有实际用途,所以放到匿名函数里利用闭包来获取他。

var inherit = (function(){
 var F = function ({}; // 闭包的私有化属性应用
 return function (Target, Origin{
  F.prototype = Origin.prototype;
  Target.prototype = new F();
  Target.prototype.constuctor = Target;
  target.prototype.uber = Origin.prototype; 
 }
}());




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


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

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

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

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

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

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

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

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

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

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

● Vue + TypeScript 踩坑总结

浏览 45
点赞
评论
收藏
分享

手机扫一扫分享

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

手机扫一扫分享

分享
举报