浅谈原型实现继承的原理

在我上一篇文章中可以了解到原型是可以用来共享方法的。

继承看字面也知道就是把父类中的属性方法传递到子类中,子类不用重复写相同逻辑的代码,这样能大大提高代码的可读性和简洁性。和我们现在可以用es6提供的class关键字创建类以及使用extends来继承父类不同。

在es6之前我们是通过构造函数模拟创建类,这种方式叫做构造函数继承,而因为原型可以共享方法,那么也可以使用原型链来实现继承,不仅仅是继承方法,也可以继承原型上的属性,毕竟原型上的方法和属性都能够被相应的实例对象通过原型链继承,所以这种方式被称为原型链继承

但是一般情况下都是在原型上定义共享的方法,而属性的继承一般通过构造函数来实现。这种用构造函数实现属性的继承,用原型实现方法的继承叫做组合继承。组合继承也是面试时会常问到的问题,因为它能考察你对原型和原型链的理解程度。

下面简述组合继承的实现方法:

对于属性的继承是通过构造函数,在子构造函数中通过直接调用父构造函数在通过call方法将父构造函数中的this修改为子构造函数中的this以此来达成继承父构造函数属性的目的。

下面是示例代码:

//先定义父构造函数
function Father(name,age){
  this.name=name,
    this.age=age
}

//定义子构造函数
function Son (name,age){
  Father.call(this,name,age)
}

var son=new Son('xm','18')
console.log(son)  //Son {name: "xm", age: "18"}

注意点:在使用构造函数继承属性的时候一定要注意this的指向问题,直接调用Father(name,age)使实现不了想要的结果的。因为直接Father(name,age)的话,父构造函数里面的this是指向Father的,在继承是Son构造函数里面调用的父构造函数的属性,这时父构造函数this应该指向子构造函数this,这样才能实现属性的继承。改变this指向的方法有三种:call,apply,bind。三种方法的区别和应用之后也会继续更新(感觉学习就是这样,学习中不断发现自己的不足,一点点地展开知识点去学习就好2333)

使用call改变this指向就相当于子构造函数里面的代码是这样的:

function Son (name,age){
  //Father.call(this,name,age)
  this.name=name,
  this.age=age
}

因为此时调用call第一个参数传入要改变this的目标对象,子构造函数里面的this当然是指向构造函数,这时父构造函数的this就改为了子构造函数的this,相当于把父构造函数里面的代码拷贝了一份给子构造函数。当要继承的属性很多的时,使用构造函数继承就很显现出其优势。

对于方法的继承是通过原型对象来实现的,通过将子构造函数的原型对象prototype=new Father()等于一个父构造函数的构造实例,这时候son.prototype指向父构造函数的构造实例,父构造函数的构造实例可以通过--proto--指向父构造函数的原型对象,通过原型链就能访问到父构造函数的方法,从而实现继承。

下面是示例代码:

  function Father(name, age) {
      this.name = name,
        this.age = age
  }
  Father.prototype.work = function () {
      console.log('今天天气真好!')
  }

  function Son(name, age) {
      Father.call(this, name, age)
  }


  Son.prototype = new Father();
  
  Son.prototype.constructor = Son;// 如果利用对象的形式修改了原型对象,要利用constructor 指回原来的构造函数
  var son = new Son('xm', '18')
  son.work()  //今天天气真好!

tips:注意不能直接让Son.prototype=Father.prototype,这时是让子原型对象指向父原型对象,这时候子原型对象修改会一起修改父原型对象,这是不符合开发需要的。

看下面错误示例代码:

  function Father(name, age) {
      this.name = name,
        this.age = age
  }
  Father.prototype.work = function () {
      console.log('今天天气真好!')
  }

  function Son(name, age) {
      Father.call(this, name, age)
  }
  Son.prototype = Father.prototype;
  Son.prototype.constructor=Son;
  Son.prototype.score=function(){
    console.log('我需要考试')
  }
  var son = new Son('xm', '18')
  console.log(Son.prototype)//{work: ƒ, score: ƒ, constructor: ƒ}
  console.log(Father.prototype)//{work: ƒ, score: ƒ, constructor: ƒ}

可以看到给子构造函数的原型定义方法会影响到父构造函数的原型。这是因为如果采用对象赋值的方式,这时是把父构造函数的原型引用地址给子构造函数的原型,两个原型都是指向同一个地址,但其中一个改变时,另外一个的原型也会发生改变。这是不符合开发需要的。这时就需要通过将子构造函数的原型对象prototype=new Father()等于一个父构造函数的构造实例,这时候son.prototype指向父构造函数的构造实例,父构造函数的构造实例可以通过--proto--指向父构造函数的原型对象,通过原型链就能访问到父构造函数的方法。这样就避免了修改子构造函数的原型影响到父构造函数的原型对象。

这就是组合继承实现模拟类实现继承的原理和方法。但组合继承也是有缺点的:调用了两次父类构造函数,生成了两份实例(子类实例将子类原型上的那份屏蔽了)解决方法是寄生组合继承。有兴趣可自行去了解。

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值