笔记-js高级程序设计-6.面向对象-继承

173 阅读3分钟

js中的继承主要是依靠原型链来实现的,基本思想是利用原型让一个引用类型继承另一个引用类型的属性和方法。

原型链

先创建一个子类一个父类:

function SuperType() {
  this.property = true
}
SuperType.prototype.getSuperValue = function() {
  return this.property
}
function SubType() {
  this.subproperty = false
}
  1. 当创建了一个新函数时,会为该函数创建一个prototype属性,指向函数的原型对象
  2. 默认所有的原型对象都会有constructor属性,指向原型对应的函数
  3. 当调用构造函数创建实例时,实例会包含一个[[prototype]]指针,指向构造函数的原型对象

根据创建对象时所说的三个要点可以画出下面的示意图: image.png

接下到了实现继承最重要的一步了:

SubType.prototype=newSuperType()\color{red}{SubType.prototype = new SuperType()} 改变子类prototype的指向,指向父类实的实例

SubType.prototype = new SuperType()

image.png

创建子类实例

// 通过原型链实现继承时,不能直接给SubType.prototype重新赋值,否则会覆盖掉继承的prototype
SubType.prototype.getSubValue = function() {
  return this.subproperty
}
var instance = new SubType()
instance.getSuperValue()   // true

image.png 当我们执行instance.getSuperValue()的时候,首先会沿着原型链向上搜索getSuperValue方法,在SuperType.prototype上找到了这个方法,然后再去找prototype属性,在SubType.prototype中找到这个属性,从而返回true。

最终的结果就是,instance指向了SubType的原型,SubType的原型又指向了SuperType的原型

原型链的默认继承

所有引用类型都默认继承了Object image.png

原型链继承的缺点

  1. 在父类原型上定义的引用类型属性,所有子类实例都共享一个,在一个实例上改变,其他实例也会改变,缺少个性化属性。
  2. 创建子类实例时,不能向父类的构造函数中传递参数。

借用构造函数

在子类构造函数中调用父类的构造函数

function SuperType() {
  this.colors = ['red', 'blue', 'green']
}
function SubType() {
  // 通过apply和call在新创建的实例上执行父类构造函数
  SuperType.call(this)
}
var instance = new SubType()

当使用new SubType()创建一个子类实例时,他会执行SuperType.call(this),这个方法会将this的值指向instance实例,就会在instance实例上添加一个colors属性,这样对于每个子类实例,都拥有自己的一份colors属性,是不共享的。

这种继承方式的重点是:SuperType.call(this)\color{red}{SuperType.call(this)}

借用构造函数继承的缺点

每个实例都拷贝了一份,占用内存大,尤其方法过多时,丢失了函数复用性。

组合继承: 原型链 + 借用构造函数

原型链用于实现对原型属性和方法的继承,借用构造函数用于实现对实例属性的继承。

function SuperType(name) {
  this.name = name
  this.colors = ['red', 'blue', 'green']
}
SuperType.prototype.sayName = function() {
  alert(this.name)
}
function SubType(age) {
  SuperType.call(this)
  this.age = age
}

SubType.prototype.constructor = SuperType
SubType.prototype.sayAge = function() {
  alert(this.age)
}
var instance = new SubType('Greg', 27) 

组合继承的缺点

无论何时,都会调用两次父类构造函数。
从代码上可以看到,分别两次调用了new SuperType(),也就是会将SuperType的实例属性添加到实例和子类原型上,虽然实例上的属性会覆盖掉子类原型上的同名属性,但其实这是不必要的。

寄生组合式继承

// 创建一个[[prototype]]指向o的空对象
function object(o){ function F(){} F.prototype = o return new F() }

function inheritPrototype(subType,superType) { 
  // 避免 prototype = new superType()造成的执行2次SuperType,这会导致 this 的不必要赋值
  var prototype = object(superType.property)
  // 下面2句重新建立原型链接
  prototype.constructor = subType
  subType.property = prototype
}
function SuperType(name) {
  this.name = name
  this.colors = ['red', 'blue', 'green']
}
SuperType.prototype.sayName = function() {
  alert(this.name)
}
function SubType(age) {
  SuperType.call(this)
  this.age = age
}
inheritPrototype(SubType,SuperType)
SubType.prototype.sayAge = function() {
  alert(this.age)
}
var instance = new SubType('Greg', 27) 

原型式继承和寄生式继承

原型式继承和寄生式继承都是基于已有的对象创建新的对象

原型式继承

es5中的Object.create()方法已经规范了原型式继承。

function create(o){
  function F(){}
  F.prototype = o
  return new F()
}
var person = {name: 'Greg',colors: ['red', 'blue', 'green']}
var p1 = create(person)