我终于真正理解了js如何实现继承

966 阅读3分钟

曾经在学习js高级程序设计中,关于js如何实现继承总结过多种实现继承的方式:像原型链、构造函数、组合式、寄生式等等。当年自认为学懂了现在想起却是脑里一片空白。我不禁反问自己,你真的懂吗?实现的每一行代码都能找到依据吗? 至此,我再次梳理了关于继承的内容从最底层的角度总结了两种方法。

🌰:d1 是 Dog 的实例,d1 拥有 Dog 的所有属性与方法。d1 也想拥有 Animal 的所有属性与方法该怎么办?让 Dog 继承了 Animal。

请先忘记你所知道的所有实现继承的方式来观看最佳哦

使用原型链 (ES5)

构造函数的结构就是把对象本身的属性写在构造函数中,把共有属性写在原型上

function Animal(legsNumber){
  this.legsNumber = legsNumber
}
Animal.prototype.run = function(){
  console.log('hi,我能跑~')
}

function Dog(name){ 
  this.name = name
  Animal.call(this, 4) // 关键代码1
}

Dog.prototype.__proto__ = Animal.prototype // 关键代码2

Dog.prototype.say = function(){
  console.log(`汪汪汪~ 我是${this.name},我有${this.legsNumber}条腿。`)
}

const d1 = new Dog('西兰花') // Dog 函数就是一个类
console.dir(d1)

继承分两部分:

  1. 继承本身的属性
 Animal.call(this, 4)  // 调用构造函数
  1. 继承共有属性(原型)即扩展 Dog 的原型

我们可以通过改写 Dog.prototype 的隐藏属性 __proto__ 就可以改变 Dog 的原型(链)

Dog.prototype.__proto__ = Animal.prototype  

!!!由于有些浏览器不是使用__proto__而是其他名称,被禁用怎么办?

也就是换一种方式实现原型链,我想到了 new

Dog.prototype = new Animal() 

new 到底干了什么 它做了以下5件事情:

  • 创建临时对象
  • 指定 this = 临时对象
  • 绑定原型(共有属性) 临时对象.__proto__ = 构造函数.原型
  • 执行构造函数(绑定自身属性)
  • 返回this 即临时对象

很明显 new 完全可以满足需求做到 绑定原型(共有属性),但是绑定自身属性是多余且错误的操作,因为Animal.call(this, 4)已经实现了:)。

是否可以借助一个空函数,把 Animal.prototype 给到空函数.prototype,那么执行这个空函数就是绑定了空的自身属性?

var f = function(){ }
f.prototype = Animal.prototype
Dog.prototype = new f()

最终使用的实现代码:

function Animal(legsNumber){
  this.legsNumber = legsNumber
}
Animal.prototype.run = function(){
  console.log('hi,我能跑~')
}

function Dog(name){ 
  this.name = name
  Animal.call(this, 4) 
}

var f = function(){ }
f.prototype = Animal.prototype
Dog.prototype = new f()

Dog.prototype.say = function(){
  console.log(`汪汪汪~ 我是${this.name},我有${this.legsNumber}条腿。`)
}

const d1 = new Dog('西兰花') // Dog 函数就是一个类
console.dir(d1) // 打印结构

有同学可能会疑惑既然 new都把事情做完了,为什么还要使用Animal.call(this, 4)绑定到 Dog 的自身属性上,然后又要创建个空数组单独处理绑定自身属性这个问题

还是看这段代码

Dog.prototype = new Animal() 

执行构造函数 Animal 会将它的自身属性 legsNumber 绑定到 Dog.prototype 上面!而不是 Dog 上!!!

使用 class(ES6)

class 的结构是把对象本身的属性写constructor中,把共有属性写在constructor外面

class Animal{
  constructor(legsNumber){
    this.legsNumber = legsNumber
  }
  run(){}
}
class Dog extends Animal{    // 关键代码1
  constructor(name) {
    super(4)    // 关键代码2
    this.name = name
  }
  say(){
    console.log(`汪汪汪~ 我是${this.name},我有${this.legsNumber}条腿。`)
  }
}

继承分两部分:

  1. 继承本身的属性 ---- super (调用父类构造函数)
  2. 继承共有属性(原型)---- extends