构造函数实现继承的几种方案(深入)

  • 什么是继承及继承的好处
  • 实现继承的几种方式
    • 原型链继承
    • 接用构造函数实现继承
    • 寄生式继承
    • 寄生组合式继承(最终的继承方案)

什么是继承及继承的好处:

​ 面向对象中的三大特性:封装,继承,多态,继承就是继承父构造函数的属性和方法。这样的话就可以实现代码的复用,且继承是多态的前提。
开篇说重点:

  1. 文中Person为父构造函数,Stundet为子构造函数,我们现在要做的事情就是让Stundet的实例对象继承父构造函数定义的属性和方法。
  2. 每个函数都有一个显示原型prototype属性,每个对象都有一个隐式原型__proto__属性,且函数也是一个对象所以它也会有__proto__属性。
  3. 查看文本首先需要你具体原型链的知识,new创建实例对象的过程,call()的作用。
  4. 文本介绍实现继承的几种方式,各种的优劣,最终会有完美实现继承的方案。
  5. 本人是菜鸡一枚,欢迎大家指正,共同进步,谢谢~
// 父类:公共的属性和方法
function Person() {
  this.name = 'lhq'
}

Person.prototype.eating = function() {
  console.log(this.name + 'eating')
}

// 子类: 特有的属性和方法
function Student() {
  this.sno = '111'
}

Student.prototype.studying = function() {
  console.log(this.name + ' studying')
}

const stu = new Student()
 // 这个显然是取不到值的,因为Student构造函数中并没有定义name属性,那他的实例对象中必然是没有该属性的
 // 那我们如果想要继承Person构造函数中的name属性该如何做呢?
console.log(stu.name)
stu.studying()

​ 下图是上面这边代码在内存中的引用关系:构造函数实现继承的几种方案(深入)_第1张图片

​ stu对象查找name属性,现在自己对象上找,找不到的话会去Student的原型对象上找,再找不到会向Obejct的原型对象中找,因为Obejct原型对象是最顶层的对象,若再找不到则会返回最终的结果。

原型链的继承方案

​ 下面这边代码还是使用上面的那段代码,只是做了一个微小的调整。

​ 1.在我们定义Student这个构造函数后,实例化Person构造函数,实例化构造函数会返回一个实例对象,将这个实例对象赋值给Student的prototype属性,让这个属性的引用指向这个对象。

​ 2.当我们通过new关键字实例化Student构造函数的时候,会返回一个实例对象stu,因为stu是一个对象,它会有一个隐式的原型对象,而这个隐式的原型对象的指向是有Student.prototype决定的。

​ 3.因为我们在实例化创建stu之前,已经将Student.prototype指向了Person的实例对象p,所以stu的隐式原型对象会指向p这个对象。

// 父类:公共的属性和方法
function Person() {
  this.name = 'lhq'
}

Person.prototype.eating = function() {
  console.log(this.name + 'eating')
}

// 子类: 特有的属性和方法
function Student() {
  this.sno = '111'
}

// 创建出Student类后做这个赋值操作
const p = new Person()
Student.prototype = p

Student.prototype.studying = function() {
  console.log(this.name + ' studying')
}

const stu = new Student()
console.log(stu.name)
stu.studying()

// 第一个弊端: 只打印本对象上的属性,继承的属性看不到
console.log(stu)

// 第二个弊端:创建出来两个实例对象

const stu1 = new Student()
const stu2 = new Student()

// 直接修改对象上的属性,是给本对象上添加一个新属性,不会修改原型引用上的name
stu1.name = 'zhangsan'

console.log(stu2.name)

// 获取引用,修改应用中的值,会互相影响

stu1.friends.push('lisi')
console.log(stu2.friends)

// 第三个弊端:无法传递参数

注意:Student的实例对象并不是一定要指向Student默认的原型对象的,这个要看构造函数中的prototype指向谁,在创建实例对象的过程中,prototype的引用会赋值给实例对象的隐式原型对象

当Student的默认原型对象没人引用它的时候,gc会将其删除

将Perosn的实例对象赋值给Stundent.prototype后的内存引用关系图:
构造函数实现继承的几种方案(深入)_第2张图片

借用构造函数实现继承的方案

这个方案决解了,原型链继承存在的弊端,但是该方案也产生了一些弊端。

// 父类:公共的属性和方法
function Person(name, age, friends) {
  this.name = name
  this.age = age
  this.friends = friends
}

Person.prototype.eating = function() {
  console.log(this.name + 'eating')
}

function Student(name, age, friends, sno) {

	// 通过call方法调用Person对象,并传递参数,call方法的作用就是改变函数内部的this指向
	// 当我们通过new Student的时候会执行这个函数的调用
  Person.call(this, name, age, friends)
  this.sno = sno
}

const p = new Person()
Student.prototype = p

Student.prototype.studying = function() {
  console.log(this.name + ' studying')
}

const stu = new Student('lhq', 18, 'zhangsan', 1110)
console.log(stu)

const stu1 = new Student('lhq', 18, 'zhangsan', 1110)
console.log(stu1)

/**
 * 借用构造函数存在的弊端
 * 
 * 1.第一个弊端:Person函数至少会被调用两次 person.call()   new Person()
 * 2.第二个弊端:p的原型对象上会多出一些属性,但是这些属性是没有存在的必要的在我们new Person创建实例对象的时候添加
 */

上述代码在内存中的引用关系图:
构造函数实现继承的几种方案(深入)_第3张图片

寄生式继承

​ 实现对象和对象之间的继承。

// 原型式
const personObj = {
  running() {
    console.log('running')
  }
}

// 工厂函数
function createStudent(name) {
  const stu = Object.create(personObj)
  stu.name = name
  stu.studying = function() {
    console.log('studying')
  }
}

const stuObj = createStudent('lhq')
const stuObj1 = createStudent('zhangsan')

弊端:创建出来的对象都有一个重复的方法
寄生组合式继承:(继承的最终方案)
function Person(name, age, height) {
  this.name = name
  this.age = age
  this.height = height
}

Person.prototype.running = function() {
  console.log('running')
}

Person.prototype.eating = function() {
  console.log('eating')
}

function Student(name, age, height, sno, score) {
  Person.call(this, name, age, height)
  this.sno = sno
  this.score = score
}

// 创建一个对象,这个对象的隐式原型指向Person的原型对象,将创建出来的对象赋值给Student的原型对象
Student.prototype = Object.create(Person.prototype)

// 将constructor的指向该回Student
// Student.prototype.constructor = Student
Object.defineProperty(Student.prototype, 'constructor', {
  enumerable: false,
  configurable: true,
  writable: true,
  value: Student
})

Student.prototype.studying = function() {
  console.log('studying')
}


const stu = new Student('lhq', 18, 1.88, 1101, 100)
console.log(stu)

​ 将创建对象的函数封装:

// 工具函数
function inheritPrototype(SubType, SuperType) {
  SubType.prototype = Object.create(SuperType.prototype)
  // 将Student.prototype.constructor指会Student
  Object.defineProperty(SubType.prototype, 'constructor', {
    enumerable: false,
    configurable: true,
    writable: true,
    value: SubType
  })
}

function Person(name, age, height) {
  this.name = name
  this.age = age
  this.height = height
}

Person.prototype.running = function() {
  console.log('running')
}

Person.prototype.eating = function() {
  console.log('eating')
}

function Student(name, age, height, sno, score) {
  Person.call(this, name, age, height)
  this.sno = sno
  this.score = score
}

// 调用工具函数
inheritPrototype(Student, Person)

Student.prototype.studying = function() {
  console.log('studying')
}


const stu = new Student('lhq', 18, 1.88, 1101, 100)
console.log(stu)

低版本使用:

​ 上面创建对象使用了Obejct.create(),低版本可能会出现问题,可以采用下面这种方案,功能是一致的。

function createObejct(O) {
  function Fn() {}
  Fn.prototype = O
  return new Fn()
}

// 工具函数
function inheritPrototype(SubType, SuperType) {
  SubType.prototype = createObejct(SuperType.prototype)
  Object.defineProperty(SubType.prototype, 'constructor', {
    enumerable: false,
    configurable: true,
    writable: true,
    value: SubType
  })
}

function Person(name, age, height) {
  this.name = name
  this.age = age
  this.height = height
}

Person.prototype.running = function() {
  console.log('running')
}

Person.prototype.eating = function() {
  console.log('eating')
}

function Student(name, age, height, sno, score) {
  Person.call(this, name, age, height)
  this.sno = sno
  this.score = score
}

// 调用工具函数,改变内存中的引用关系
inheritPrototype(Student, Person)

Student.prototype.studying = function() {
  console.log('studying')
}


const stu = new Student('lhq', 18, 1.88, 1101, 100)
console.log(stu)
stu.running()
stu.eating()
stu.studying()

上述代码在内存中的引用关系图:
构造函数实现继承的几种方案(深入)_第4张图片

欢迎大家评论指正,共同进步~

你可能感兴趣的:(JavaScript,javascript)