js中 继承 & 原型链

174 阅读3分钟

1.png

前言

js 中的继承: 实例的属性和方法互不干扰,同时享有共同的属性和方法

js 对象属性的搜索是先找自身,然后找原型,接着原型的原型,,,Object.prototype,然后就是 null

原型链

任何函数的默认原型都是 Object() 的实例

构造函数A拥有原型对象A,通过构造函数能够创建实例a。实例上有一个内部指针a __proto__ 指向构造函数A原型对象A

如果构造函数A原型对象A是另一个构造函数B的实例,那么原型对象A上也会有一个内部指针A __proto__ 指向构造函数B原型对象B

function SuperType () {
    this.property = true
}

function SubType () {
    this.subProperty = false
}

// 原型链继承的核心
SubType.prototype = new SuperType()

const instance = new SubType()

重复上面的过程,当构造函数的原型对象是 Object() 创建的实例会遇到一个定义。

Object.prototype.__proto__ === null // true

以上实例和原型的关系,就是原型链

判断原型和实例的关系

instanceof 操作符

instanceof 可以用来校验构造函数的 prototype 是否出现在实例的原型链上。当然如果原型关系被修改,就可能得到不同的结果

Object instanceof Function // true
Array instanceof Function // true
Function instanceof Function // true

isPrototypeOf

Object.prototype.isPrototypeOf({}) // true
Function.prototype.isPrototypeOf(Array) // true
Function.prototype.isPrototypeOf(Object) // true
Function.prototype.isPrototypeOf(Function) // true

原型链的优点

  1. 共享原型上的属性和方法

原型链的问题

  1. 原型上引用类型的属性更改,会导致不可预知的问题。这个问题好像不可避免,只能认为干预
  2. 实例不能传递参数

构造函数

构造函数分为内置构造函数(如Object Promise等)和自定义构造函数

function SuperType () {
    this.age = 11
}

function SubType (name) {
    // call/apply 调用 SuperType
    // SuperType.call(this)
    SuperType.apply(this)
    this.name = name
}

const instance = new SubType('Jerry')

传递参数

和原型链继承不同的是构造函数可以传递自定义参数

构造函数的优点

  1. 能够传递参数
  2. 引用类型数据单独存在

构造函数的问题

  1. 继承的属性和方法必须定义在构造函数之内,方法不能共用(内存)
  2. 父类原型的属性和方法不能继承

组合继承

原型链 + 构造函数 = 组合继承

function SuperType (name) {
    this.name = name
}
SuperType.prototype.sayName = function () {
    return this.name
}

function SubType (name, age) {
    // 构造函数
    SuperType.call(this, name)
    this.age = age
}

// 原型链
SubType.prototype = new SuperType()

const instance1 = new SubType('Jerry', 12)

组合继承的优点

  1. 原型上的属性和方法可以继承

  2. 实例可以传递参数

组合继承的缺点

  1. 父类被调用两次(内存)

原型式继承

function object (o) {
    function F () {}
    F.prototype = o
    return new F()
}

const obj = {
    a: 1,
    arr1: [1, 2]
}
const obj1 = object(obj)
const obj2 = object(obj)

obj.a = 3
obj1.a = 2
obj1.arr1.push(3)

obj2.a // 3
obj2.arr1 // [1, 2, 3]

将 obj 这个引用类型的数据作为模版,进行复制。实例指针上的原型对象引用的是 obj,修改 obj 会影响所有实例

原型式继承的问题

  1. 引用属性数据问题

寄生式继承

function object(o) {
  function F () {}
  F.prototype = o
  return new F()
}

function createAnother(original) {
  let clone = object(original)
  clone.sayHi = function () {
    console.log('hi')
  }
  return clone
}

let person = {
  name: 'Jerry',
  friend: ['Tom', 'Lucy']
}

let anotherPerson = createAnother(person)

寄生式组合继承


function inheritPrototype(subType, superType) {
  let prototype = object(superType.prototype)

  // 将 constructor 重新指向 subType
  prototype.constructor = subType
  subType.prototype = prototype
}

function SuperType (name) {
  this.name = name
  this.colors = ['red', 'blue', 'green']
}

SuperType.prototype.sayName = function () {
  console.log(this.name)
}

SuperType.prototype.arr1 = [1, 2, 3]

function SubType (name, age) {
  SuperType.call(this, name)
  this.age = age
}

inheritPrototype(SubType, SuperType)

SubType.prototype.sayAge = function () {
  console.log(this.age)
}

const instance1 = new SubType('Jerry', 15)
const instance2 = new SubType('Jerry2', 14)

instance1.arr1.push(4)

console.log(instance1, instance2)

应用

逻辑和业务复杂且多的情况下,使用继承的概念可以便于我们梳理整合。例如开发框架,父子关系的业务。

小结

  1. 看起来寄生式继承是这六种里面最好的,但是也不能解决父类原型上放引用类型数据的问题。。。

  2. 学习理解继承可以帮助我们梳理js中 Object Function 和对应实例之间的关系,同样也是一种编程思维。当然合适的才是最好的,简单的就用一个构造函数解决