JavaScript 笔记(9)- 继承

原创 乘风逐月 随笔 JavaScript 152阅读 2018-05-15 10:43:03 举报

一、原型链

1.原型链

ECMAScript 中描述了原型链的概念,并将原型链作为实现继承的主要方法。其基本思想是:利用原型让一个引用类型继承另一个引用类型的属性和方法。
构造函数、原型、实例之间的关系:每个构造函数都有一个原型对象,每个原型对象都有一个指向构造函数的指针,每个实例都有一个指向原型对象的内部指针。如果让原型对象等于另一个类型的实例,那么这个原型将包含指向另一个原型的指针,相应的,另一个原型中也包含指向另一个构造函数的指针。假如另一个原型又是另一个类型的实例,那么上述关系依然成立,如此层层递进,就构成了实例与原型之间的链条,即原型链。
例如:

上面的代码中,instance 是 SubType 的实例。SubType 的原型是 SuperType 的实例,其内部有一个指针指向 SuperType 的原型,故 instance 就继承了 SuperType 实例所拥有的全部属性和方法。最终就是: instance 指向 SubType 的原型,SubType 的原型指向 SuperType 的原型。getSuperValue() 方法在 SuperType 的原型中,返回的 property 属性在 SubType.prototype 中,即SuperType的实例中。
原型链的实现,本质上就是原型搜索机制,当读取实例属性时,首先在实例本身找,没找到,到其指向的原型中找,一级一级向上查找,直到原型链的末端。
注:此时 instance 的 constructor 指向了 SuperType ,是因为 SubType 的原型是 SuperType 的实例,这个实例的 constructor 指向了 SuperType 。

2.默认的原型

所有引用类型都默认继承了 Object,这个继承也是通过原型链继承的。所有函数的默认原型都是 Object 的实例,因此默认原型都包含一个内部指针指向 Object.prototype 。所有所有自定义类型都会继承toString()、valueOf() 等默认方法。
那么上面的例子中,SuperType 的原型指向了 Object.prototype,如果调用 instance.toString() 方法时,调用的是 Object.prototype 中的 toString() 方法。

3.确定原型和实例的关系
a.使用 instanceof 操作符:

这个操作符可以测试实例与原型链中出现过的构造函数,结果返回 true。

由于原型链的关系,可以说 instance 是 Object,SuperType,SubType中任何一个类型的实例。故结果都是 true。

b. 使用 isPrototypeOf()方法

只要是原型链中出现过的原型,都可以说是该原型链所派生的实例原型,结果返回 true。

4.谨慎的定义方法

通过原型链实现继承时,不能使用对象字面量创建原型方法,这样做会重写原型链。

此时 SubType 的原型是一个 Object 的实例,而非 SuperType 的实例,SubType 与 SuperType之间已经没有关系了。

5.原型链的问题

a. 实例共享原型的属性方法,通过实例修改原型属性方法时,所有实例都会受其影响。
b. 创建子类型实例时,没有办法在不影响所有对象实例的情况下,给超类型的构造函数传递参数。
故实践中很少单独使用原型链。

二、继承的方式

1.构造函数

借用构造函数(也称伪造对象或经典继承),可以解决原型中包含引用类型值带来的问题。基本思想:在子类型构造函数的内部调用超类型构造函数。

上例中,会在新创建的 SubType 实例环境下调用 SuperType 构造函数,这样每个 SubType 实例都有自己的 colors 副本。

a.优点

相对于原型链,借用构造函数的优点是:可以在子类型构造函数中向超类型构造函数传递参数

b.问题

方法都在构造函数中定义,则不能复用函数。

2.组合继承

组合继承(也称伪经典继承),是指将原型链和借用构造函数的技术组合到一起,发挥二者之长的一种继承模式。
思路是:使用原型链实现对原型属性和方法的继承,使用构造函数实现对实例属性的继承。这样通过原型上定义方法,实现了方法复用,又保证了每个实例都有自己的属性。
例如:

问题:会调用两次超类型构造函数,在第一次调用 SuperType 构造函数时,SubType.prototype 会得到两个属性:name 和 colors,,他们都是 SuperType 的实例属性,位于 SubType 的原型中。调用 SubType 创建新对象时会第二次调用 SuperType ,这次又在新对象上创建了实例属性 name 和 colors,这两个属性就屏蔽了原型中的两个同名属性。

3.原型式继承

思路:借助原型基于已有的对象创建新对象,不必因此创建自定义类型。

上例中,已有对象是 person 对象,传入到 object() 函数中,然后该函数会返回一个新对象。新对象将 person 作为原型,意味着 person.colors 不仅属于自己所有,而且会被 person1 和 person2 共享。
Object.create()方法规范化了原型式继承。这个方法接受两个参数:一个作为新对象的原型的对象和(可选的)一个为新对象定义额外属性的对象。在传入一个参数的情况下,Object.create()与object()方法作用相同。
Object.create()方法的第二参数与Object.defineProperties()方法上的第二个参数格式相同:,每个属性都是通过自己的描述符定义的,以这种方式指定的任何属性都会覆盖原型对象上的同名属性。
在想让一个对象与另一个对象保持类似的情况下,可以使用原型式继承。但是引用类型的值始终都会共享。

4.寄生式继承

寄生式继承的思路与寄生构造函数和工厂模式类似,即创建一个封装继承过程的函数,该函数在内部以某种方式来增强对象,最后返回对象。
例如:

使用寄生式继承来为对象添加方法,方法是不能复用的,与构造函数模式类似。

5.寄生组合式继承

前面说了 组合继承 的问题,会两次调用超类型构造函数,会在子类型构造函数上创建重复的属性,寄生式组合继承 可以解决这个问题。
寄生式组合继承,即通过借用构造函数来继承属性,通过原型链的混成形式来继承方法。其背后的思路是:不必为了指定子类型的原型而调用超类型的构造函数,只需要超类型原型的一个副本。本质就是使用寄生式继承来继承超类型的原型,然后再将结果指定给子类型的原型。
例如:

上例中: inheritPrototype 函数实现了寄生组合式继承的最简单形式。这个函数接受两个参数:子类型构造函数和超类型构造函数。函数内,第一步是创建创建超类型原型的一个副本,第二部是为新创建的对象添加 constructor 属性(因为在object方法中,重写了 prototype 的原型失去了默认的 constructor 属性),第三步,将新创建的对象赋值给子类型的原型。

这个例子的高效率体现在它只调用了一次 superType 构造函数,并且避免了在 subType.prototype 上面创建不必要的、多余的属性。同时原型链还能保持不变,可以 正常使用 instanceOf 和 isPrototypeOf()。

评论 ( 1 )
最新评论