深度剖析Javascript继承

目录

1. 原型链

原型链的问题:

2. 借用构造函数

借用构造函数的问题:

3. 组合式继承

组合式继承的问题:

4. 原型式继承

5. 寄生式继承

6. 寄生组合式继承

总结


1. 原型链

此种继承方式的基本思想是:利用原型让一个引用类型继承另一个引用类型的属性和方法。

  function Parent() {
    this.prop = true
  }
  Parent.prototype.getProp = function () {
    return this.prop
  }

  function Son() {
    this.subProp = false
  }
  Son.prototype = new Parent()
  Son.prototype.getSubProp = function () {
    return this.subProp
  }
  let instance = new Son
  console.log(instance)

输出如下:

原型链继承

使用此方法实现要注意一点:若在重写原型链之前创建对象,则这个对象不会拥有后面重写原型链之后的方法和属性,我们看下一下下面的代码:

  function Parent() {
    this.prop = true
  }
  Parent.prototype.getProp = function () {
    return this.prop
  }

  function Son() {
    this.subProp = false
  }
  let s = new Son()
  
  Son.prototype = new Parent()
  Son.prototype.getSubProp = function () {
    return this.subProp
  }
  let instance = new Son
  console.log(s, '重写原型链之前')
  console.log(instance, '重写原型链之后')

输出如下:

原型链重写前后

可以很明显的看到,重写原型链之前创建的对象s的原型并不会属性prop和方法getSubProp,所以,如果你要用此方法来实现继承,请务必在重写原型链之后再创建新对象。

原型链的问题:

(1)原型链实现继承,最主要的问题来自包含引用类型值的原型,请看下面代码:

  function Parent() {
    this.colors = ['red', 'blue']
  }

  function Son() {
  }

  Son.prototype = new Parent()

  let s1 = new Son()
  s1.colors.push('green')
  console.log(s1)

  let s2 = new Son()
  console.log(s2)

输出如下:

可以很明显的看到,第二次创建的实例s2的原型中的属性colors中也有了"green"。其实,引用类型值的原型属性会被所有实例共享,而这也正是为什么要在构造函数中,而不是再原型对象中定义属性的原因。

(2)原型链的第二个问题是:再创建子类型的实例时,不能向超类型的构造函数中传递参数。实际上,应该说是没有办法在不影响所有对象实例的情况下,给超类型的构造函数传递参数。

基于以上两点,我们一般不建议使用原型链的方式实现继承,实际上大多数人也不会单独采用这种方式。

2. 借用构造函数

在解决原型中包含引用类型值带来问题的过程中,开发人员开始使用一种叫做“借用构造函数”的技术(有时候也叫伪造对象或经典继承)。这种技术的基本思想相当简单,即在子类型构造函数内部调用超类型构造函数。

  function Parent() {
    this.list = ['football', 'baseball']
  }

  function Son() {
    Parent.call(this)
  }

  let instance1 = new Son()
  instance1.list.push('tennis')
  console.log(instance1)

  let instance2 = new Son()
  console.log(instance2)

输入如下:

可以很明显的看到,实例中的引用类型值不再互相影响,由此解决原型链实现继承带来的引用类型值的问题。此外,相比原型链而言,借用构造函数还有一个很大的优势,即可以在子类型构造函数中向超类型构造函数传递参数。请看下面代码:

  function Parent(name) {
    this.name = name
  }

  function Son() {
    Parent.call(this, 'Tom')
    this.age = 15
  }

  let instance = new Son()
  console.log(instance)

输出如下:

这里我们要提醒各位的是,为了确保超类型构造函数不会重写子类型的属性,所以我们建议,请在调用超类型构造函数之后,再添加子类型中定义的属性。

借用构造函数的问题:

很明显的可以发现,这种模式实现的继承,子类型无法获得超类型的原型中定义的属性和方法。基于上述考虑,我们一般也很少单独使用此种方式实现继承。

3. 组合式继承

组合继承,有时候也叫伪经典继承,指的是通过将原型链和借用构造函数的技术组合在一起,取二者之所长实现继承的一种技术。其思路是使用原型链实现对原型属性和方法的继承,而通过借用构造函数来实现对实例属性的继承。由此,既通过在原型上定义方法实现了函数复用,又能保证每个实例都有自己的属性。请看下面的代码:

  function superType(name) {
    this.name = name
    this.colors = ['red', 'green', 'blue']
  }
  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.constructor = subType
  subType.prototype.sayAge = _ => {console.log(this.age)}
  
  let sub1 = new subType('Tom', 15)
  console.log(sub1)

输出如下:

组合式继承的问题:

组合式继承虽然融合了原型链和借用构造函数两者的优势,解决了二者单独使用时带来的问题,但是此种方式也不是完美无缺的。我们观察上述输出,不难发现,此种方式实现继承的过程中,调用了两次超类型构造函数;一次是在创建子类型原型的时候,另一次是在子类型构造函数内部。进而就出现了,就上述例子来说,可以发现超类型构造函数中的属性colors和name,既出现在子类实例属性中,也出现在子类原型链上。

4. 原型式继承

利用ES5新增的方法Object.create()实现原型式继承,这个方法接收两个参数:第一个参数用于作为新对象的原型,第二个参数(可选)用于为新对象定义额外的属性;其中,第二个参数与Object.defineProperties()方法的第二个参数格式相同:每个属性都是通过自己的属性描述符定义的。

  let person = {
    area: 'China',
    list: ['football', 'baseball']
  }
  let p = Object.create(person)
  console.log(p)

输入如下:

原型式继承

使用此种方式实现继承,所带来的问题和原型链方式一样,同样是引用类型值的问题。

5. 寄生式继承

此种方式的思路与寄生构造函数和工厂模式类似,即创建一个仅用于封装继承过程的函数,该函数在内部以某种方式来增强对象。请看下面代码:

  function create(obj) {
    let clone = Object.create(obj)
    clone.sayName = function () {
      console.log('Hello world')
    }
    return clone
  }

  let person = {
    name: 'Tom',
    list: ['baseball', 'football']
  }
  let p = create(person)
  p.sayName()   // Hello world

此种方式与构造函数模式类似,无法做到函数复用而降低效率。

6. 寄生组合式继承

所谓寄生组合式继承,即通过借用构造函数来继承属性,通过原型链的混成形式来继承方法。此种方式弥补了组合式继承方式带来的缺陷,请看下面代码:

  function superType(name) {
    this.name = name
    this.colors = ['red', 'green', 'blue']
    this.pi = function () {
      console.log(123)
    }
  }
  superType.prototype.sayName = function () {
    console.log(this.name)
  }
  function subType(name, age) {
    superType.call(this, name)
    this.age = age
  }

  subType.prototype = Object.create(superType.prototype)
  subType.prototype.constructor = subType
  subType.prototype.sayAge = _ => {console.log(this.age)}
  
  let sub1 = new subType('Tom', 15)
  console.log(sub1)

输出如下:

可以很明显的看出,此种方式只调用一次超类型构造函数,因此避免了在原型链上创建多余的属性。

总结

业界普遍认为寄生组合式继承是引用类型最理想的继承模式。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值