搞透JS中的继承

3964次阅读 212人点赞 作者: WuBin 发布时间: 2022-01-24 17:13:26
扫码到手机查看

使用原型链实现继承

在 JS 中并不存在类,class只是语法糖,本质还是函数。所以在JS中的继承方式中,本质上就是在重写原型对象。

继承实现的本质就是重写原型对象

我们先来看一下ES5中的继承方式。

function SuperType(){
    this.colors = ['red','blue','green']
}
function SubType(){
    // 省略
}
// 此处就是在重写子类的原型对象
SubType.prototype = new SuperType()
var instance1 = new SubType()
instance1.colors.push('aaa'); 
console.log(instance1.colors)//['red','blue','green','aaa']
var instance2 = new SubType()
console.log(instance2.colors)//['red','blue','green','aaa']

这种实现方式存在以下问题:

问题一: 当原型链中包含引用类型值的原型时,该引用类型值会被所有实例共享;

问题二: 在创建子类型(例如创建Son的实例)时,不能向超类型(例如Father)的构造函数中传递参数.

注意:Child.prototype = new Parent()这段代码重写了Child的constructor属性,Child.prototype.constructor === Parent; //true所以如果使用var child2 = new Child()的话,child2的构造函数也不再是Child,而是Parent,也就是说此时child2.constructor === Parent;//true child2.constructor=== Child; //false(这里的实例child2不是函数,而是对象,因此没有prototype属性)

借用构造函数实现继承

function SuperType(){
    this.colors = ['red','blue','green']
}
function SubType(){
    //继承了SuperType,使得以后使用new SubType()创造出来的示例会调用SuperType()
    SuperType.call(this)
}
SubType.prototype = new SuperType()
var instance1 = new SubType()
instance1.colors.push('aaa'); 
console.log(instance1.colors)//['red','blue','green','aaa']
var instance2 = new SubType()
console.log(instance2.colors)//['red','blue','green']

这种模式还可以向父类传递参数:

function SuperType(name){
    this.name = name
}
function SubType(){
    //继承了父类的时候,同时还向父类传递了参数
    SuperType.call(this,"nike")
    this.age = 29
}
var instance = new SubType();
console.log(instance.name);  //nike
console.log(instance.age);   //29

借用构造函数实现继承存在以下问题: 问题一:每次创建一个 Child 实例对象时候都需要执行一遍 Parent 函数 问题二:函数不可复用。父类中定义的方法,对子类型而言是不可见的. 每个实例都拷贝一份,占用内存大。 这里要特别解释一下这里的函数不可复用 假设SuperType 在构造函数里定义了一个方法:

function  SuperType(name){
    this.colors=["red","blue","green"];
    this.name=name;
    this.sayName = function() {
        console.log(this.name);
    };
}

那么每一次调用new SuperType,就会在实例内部定义一次这个方法——你定义1000个实例,就会定义1000次这个方法;对于color 和name 这两个变量来说,这无可厚非;但实例方法大多数是相同的,所以这里更推荐把方法定义在SuperType.prototype上,这样每个实例构造出来就自动继承这个方法,不用在构造函数里一次次地写。

组合继承

指的是将原型链和借用构造函数的技术组合到一块,从而发挥两者之长的一种继承模式.

function Father(name){
    this.name = name;
    this.colors = ["red","blue","green"];
}
Father.prototype.sayName = function(){
    alert(this.name);
};
function Son(name,age){
    Father.call(this,name);//继承实例属性,第一次调用Father()
    this.age = age;
}
Son.prototype = new Father();//继承父类方法,第二次调用Father()
Son.prototype.sayAge = function(){
    alert(this.age);
}
var instance1 = new Son("louis",5);
instance1.colors.push("black");
console.log(instance1.colors);//"red,blue,green,black"
instance1.sayName();//louis
instance1.sayAge();//5

var instance1 = new Son("zhai",10);
console.log(instance1.colors);//"red,blue,green"
instance1.sayName();//zhai
instance1.sayAge();//10

组合继承避免了原型链和借用构造函数的缺陷,融合了它们的优点。 问题:组合继承其实调用了两次父类构造函数, 造成了不必要的消耗。一次是在创建子类型原型的时候, 另一次是在子类型构造函数内部.

寄生组合式继承

function Parent(value) {
  this.val = value
}
Parent.prototype.getValue = function() {
  console.log(this.val)
}

function Child(value) {
  Parent.call(this, value)
}
// 不必为了指定子类型的原型而调用父类型的构造函数,我们所需要的无非就是父类型原型的一个副本而已。
Child.prototype = Object.create(Parent.prototype, {
  constructor: {
    value: Child,
    enumerable: false,
    writable: true,
    configurable: true
  }
})

const child = new Child(1)

child.getValue() // 1
child instanceof Parent // true

现在开发场景我们通常都是能使用ES6都是用ES6实现继承了。

Class继承

class Parent {
  constructor(value) {
    this.val = value
  }
  getValue() {
    console.log(this.val)
  }
}
class Child extends Parent {
  constructor(value) {
    super(value)
    this.val = value
  }
}
let child = new Child(1)
child.getValue() // 1
child instanceof Parent // true

class实现继承的核心在于使用extends表明继承自哪个父类,并且在子类构造函数中必须调用super,因为这段代码可以看成Parent.call(this, value)

手动现实extend

function extend(subClass,superClass){
    var prototype = Object(superClass.prototype);//创建对象
    prototype.constructor = subClass;//增强对象
    subClass.prototype = prototype;//指定对象
}

extend的高效率体现在它没有调用superClass构造函数,因此避免了在subClass.prototype上面创建不必要,多余的属性. 于此同时,原型链还能保持不变; 因此还能正常使用 instanceof 和 isPrototypeOf() 方法.

总结与面试题

类的继承就是两点:

1.子类调用父类的构造函数(因为子类要获得父类的属性和方法,所以需要这一步),在es5中是通过在子类的构造函数中使用call来调用父类的构造方法,在es6中则是通过super方法实现的)

2.更改子类的原型链(es5中更改原型链的方式是通过Child.prototype = new Parent()等方式,es6中则是通过extends关键字的方式来实现的)

继承相关的面试题

1.手写ES5或者ES6的继承方式并指出其中的优缺点? 见上

2.ES5和ES6在继承的实现上有什么区别?

ES5的继承实质上是先创建子类的实例对象,然后再将父类的方法添加到this上(Parent.apply(this))

ES6的继承机制实质上是先创建父类的实例对象this(所以必须先调用父类的super()方法),然后再用子类的构造函数修改this.通过关键字class定义类, extends关键字实现继承. 子类必须在constructor方法中调用super方法否则创建实例报错. 因为子类没有this对象, 而是使用父类的this, 然后对其进行加工。super关键字指代父类的this, 在子类的构造函数中, 必须先调用super, 然后才能使用this。

点赞 支持一下 觉得不错?客官您就稍微鼓励一下吧!
关键词:js,继承
推荐阅读
  • uniapp实现被浏览器唤起的功能

    当用户打开h5链接时候,点击打开app若用户在已经安装过app的情况下直接打开app,若未安装过跳到应用市场下载安装这个功能在实现上主要分为两种场景,从普通浏览器唤醒以及从微信唤醒。

    9094次阅读 588人点赞 发布时间: 2022-12-14 16:34:53 立即查看
  • Vue

    盘点Vue2和Vue3的10种组件通信方式

    Vue中组件通信方式有很多,其中Vue2和Vue3实现起来也会有很多差异;本文将通过选项式API组合式API以及setup三种不同实现方式全面介绍Vue2和Vue3的组件通信方式。

    3843次阅读 286人点赞 发布时间: 2022-08-19 09:40:16 立即查看
  • JS

    几个高级前端常用的API

    推荐4个前端开发中常用的高端API,分别是MutationObserver、IntersectionObserver、getComputedstyle、getBoundingClientRect、requ...

    14071次阅读 914人点赞 发布时间: 2021-11-11 09:39:54 立即查看
  • PHP

    【正则】一些常用的正则表达式总结

    在日常开发中,正则表达式是非常有用的,正则表达式在每个语言中都是可以使用的,他就跟JSON一样,是通用的。了解一些常用的正则表达式,能大大提高你的工作效率。

    12908次阅读 442人点赞 发布时间: 2021-10-09 15:58:58 立即查看
  • 【中文】免费可商用字体下载与考证

    65款免费、可商用、无任何限制中文字体打包下载,这些字体都是经过长期验证,经得住市场考验的,让您规避被无良厂商起诉的风险。

    11469次阅读 920人点赞 发布时间: 2021-07-05 15:28:45 立即查看
  • Vue

    Vue3开发一个v-loading的自定义指令

    在vue3中实现一个自定义的指令,有助于我们简化开发,简化复用,通过一个指令的调用即可实现一些可高度复用的交互。

    15588次阅读 1244人点赞 发布时间: 2021-07-02 15:58:35 立即查看
  • JS

    关于手机上滚动穿透问题的解决

    当页面出现浮层的时候,滑动浮层的内容,正常情况下预期应该是浮层下边的内容不会滚动;然而事实并非如此。在PC上使用css即可解决,但是在手机端,情况就变的比较复杂,就需要禁止触摸事件才可以。

    14795次阅读 1205人点赞 发布时间: 2021-05-31 09:25:50 立即查看
  • Vue

    Vue+html2canvas截图空白的问题

    在使用vue做信网单页专题时,有海报生成的功能,这里推荐2个插件:一个是html2canvas,构造好DOM然后转canvas进行截图;另外使用vue-canvas-poster(这个截止到2021年3月...

    28950次阅读 2273人点赞 发布时间: 2021-03-02 09:04:51 立即查看
  • Vue

    vue-router4过度动画无效解决方案

    在初次使用vue3+vue-router4时候,先后遇到了过度动画transition进入和退出分别无效的情况,搜遍百度没没找到合适解决方法,包括vue-route4有一些API都进行了变化,以前的一些操...

    24982次阅读 1925人点赞 发布时间: 2021-02-23 13:37:20 立即查看
交流 收藏 目录