JS继承

129 阅读3分钟

原型链继承

首先我们得知道什么是原型链。原型就是一个构造函数有prototype这个属性,这个属性指向的是一个对象,而这个对象就是构造函数的原型;通过new 构造函数生成的实例中有__proto__属性,这个__proto__属性指向原型对象。那原型链是啥呢?首先我们想一个问题:函数是一个对象,对象有原型,原型也是对象,那原型是不是也有对应的原型呢。开始套娃,就形成了原型链。原型链的顶端是啥呢,所有的对象都是通过Object函数new出来的,那顶端就是Object对象,Object的__proto__就指向null。

言归正传,说一下原型链继承

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

function Student(age) {
  this.age = age
}

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

let stu = new Student(20)
stu.studying()

Student子类的构造函数的原型指向Person父类的实例对象,这样就继承了父类Person的原型上的数据。画个图吧

image.png

这样很明显有很多缺点:

  1. 只会继承父类原型上的属性,并不会继承父类上的属性
  2. 修改父类原型上的属性时,都会受到影响

借用构造函数继承

借用call来改变this指向,继承父类的属性。

function Person(n, m) {
  this.eating = function () {
    console.log('eating')
  }
  this.name = n
  this.class = m
}

function Student(age, n, m) {
  this.age = age
  Person.call(this, n, m)
}

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

let stu = new Student(20, 'wkb', '一班')
console.log(stu)

我们可以看到在Student构造函数中调用Person的构造函数,并通过call来改变Person函数this指向,然后赋值,这样就可以在原型链继承的基础上,继承父类Person的属性。

但是,这样依然有很多缺点:

  1. 调用了两次Person构造函数,造成性能的浪费。
  2. 会继承了父类不必要的属性

原型式继承

首先我们要达到继承父类的原型上的属性,必须要新建一个对象,该对象指向父类的原型,子类的原型指向该对象。这样才不会生成多余的属性。我们有三种方法来实现:

  1. 原型函数
function createPrototype(obj) {
  let fn = function () {}
  fn.prototype = obj
  let newObj = new fn()
  return newObj
}
  1. Object.setPrototypeOf
function createPrototype2(obj) {
  let newObj = {}
  Object.setPrototypeOf(newObj, obj)
  return newObj
}
  1. Object.create
let info = Object.create(Person)

这三种都是实现了创建一个空对象,空对象的原型指向父类,然后返回这个空对象

寄生式继承

通过Object.create来继承父类原型上的熟悉

let person = {
  eating: function () {
    console.log('eating')
  },
}

function createStudent(name) {
  // 内部创建了一个对象,对象的原型是传入的对象,然后返回创建的对象
  var stu = Object.create(person)
  stu.name = name
  stu.studying = function () {
    console.log('studying')
  }
  return stu
}

let stuObj = createStudent('wkb')

console.log(stuObj)
stuObj.eating()

这种方法很可以继承父类原型上的属性,但是只适用于Object对象,不适用于构造函数

寄生组合式继承

相对比上面提到的,这种方法是最好继承方法。


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

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

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

function Student(name, age, className, studentId) {
  Person.call(this, name, age)
  this.className = className
  this.studentId = studentId
}

Student.prototype = Object.create(Person.prototype)
Student.prototype.constructor = Student // 不重新指向话,打印出来的stu是Person类型

let stu1 = new Student('wkb', 20, 'compute', '20181251150')

stu1.eating()

通过call继承Person父类内部的属性,再通过Object.create继承Person父类原型上的属性。注意一点:在修改Student原型指向后,我们打印Student的实例stu1,则发现是Person类型,这是因为,Student的原型被修改,则对应的constructor的指向也被修改。当前Studnet的原型是一个空对象,空对象的原型是Person父类的原型,Person父类的原型中有Constructor属性,指向Person构造函数,则打印出来的则是Person类型。 如下图

image.png

我们发现这种只适用一种继承,我们可以封装一个函数,来达到复用的目的。如下


function inherit(subObj, superObj) {
  let Fn = function () {}
  Fn.prototype = superObj.prototype
  subObj.prototype = new Fn()

  Object.defineProperty(subObj.prototype, 'constructor', {
    enumerable: false,
    writable: true,
    configurable: true,
    value: subObj,
  })
}