继承(Javascript)

2021-11-29  本文已影响0人  东方三篇

继承

继承是面相对象编程中讨论最多的话题。 很多面相对象语音都支持两种继承: 接口继承和实现继承。前者只继承方法签名, 后者继承实际方法。接口继承在ECMAScript中不能实现,因为函数没有签名。 javascript中只有 实现继承 方法来实现继承,这个继承主要是通过原型链来实现的。

1. 原型链

**原型链**是实现js继承的主要方式。原理是通过原型链继承多个引用类型的属性和方法。 简述一下构造函数,原型和实例的关系: 每个构造函数都有一个原型对象,原型对象有一个属性指回构造函数, 实例有一个内部指针指向原型。![原型关系.png](https://img.haomeiwen.com/i22578326/a82279347bed9cb9.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

如果原型对象是另一个类型的实例, 那就意味着这个原型本身有一个内部指针指向另一个原型, 相应的另一个原型也有一个指针指向另一个构造函数。 这样实例和原型之间就构造一条原型链。 这就是原型链的基本构想。

// 实现原型链设计代码模式
const SuperType = function () {
    this.property = true
}
SuperType.prototype.getSuperValue = function () {
    return this.property
}
const SubType = function () {
    this.sunproperty = false
}
// 继承SuperType  SubType 通过创建 SuperType 的实例并将其赋值给自己的原型 SubTtype.prototype 实现了对 SuperType 的继承
SubType.prototype = new SuperType() // 这个赋值重写了 SubType 最初的原型,将其替换为SuperType 的实例
SubType.prototype.getSubValue = function () {
    retrurn this.subproperty
}
const instance = new SubType()
console.log(instance.getSuperValue()) // true SubType的实例能够访问到 SuperType上的方法或属性, 是因为 SubType的原型继承了 SuperType的实例
原型链图.png
a. 默认原型
实际上,原型链中还有一环,默认情况下, 所有引用类型都继承自Object, 这也是通过原型链实现的。任何函数的默认原型都是一个Object的实例, 这个实例有一个内部指针指向Object.prototype。这也就是自定义类型能够继承包括toString(), valueOf()等在内的默认方法。下图描述了完整的原型链:![完整原型链.png](https://img.haomeiwen.com/i22578326/5462a7df5e38717e.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
b. 原型与继承关系

原型与实例间的关系可以通过两个方式来确定。 第一种方法instanceof 操作符, 如果一个实例的原型链中出现过响应的构造函数,则返回 true。

console.log(instance instanceof Object) // true
console.log(instance instanceof SuperType) // true
console.log(instance instanceof SubType) // true

第二种方法还是用 isPrototypeOf()方法。原型链中的没有过原型都能调用这个方法, 只要原型链中包含这个原型,就返回 true。

Object.prototype.isPrototypeOf(instance) // true
SuperType.prototype.isPrototypeOf(instance) // true
SubType.prorotype.isProrotypeOf(instance) // true
c. 关于方法

子类有时候需要覆盖父类的方法,或者增加父类没有的方法。 这些方法必须在原型赋值之后再添加到原型上。

d. 原型链的问题

原型中包含的引用值会在所有实例间共享, 这也是为什么属性通常会在构造函数中定义,而不是定义在原型上的原因。还有, 子类型在实例化时不能给父类型传参。所以原型链基本不会被单独使用。

2. 盗用构造函数

为了解决原型包含引用值导致的继承问题, 一种叫做“盗用构造函数(对象伪装或经典继承)”技术流行起来。基本思路是在子类构造函数中调用父类的构造函数。可以通过 apply()和call()方法以新创建的对象为上下文执行构造函数。

call()和apply()作用都是指定this指向,只是传入的参数不同而已

const sum = function (num1, num2) {
    return num1 + num2;
}
const callSum = function (num1, num2) {
    return sum.call(this, num1, num2) // call 参数以此传入
}
const applySum = function (num1, num2) {
    return sum.apply(this, [num1, num2]) // apply传入数组
}

实现盗用构造函数的示例:

// 盗用
const SuperType = function () { this.colors = ['red', 'blue', 'green'] }
const SubType = function () { SuperType.call(this) } // 继承 SuperType, 盗用构造函数的调用
const instance1 = new SubType()
instance1.colors.push('black')
console.log(instance1.colors) // ['red', 'blue', 'green', 'black']
const instance2 = new SubType()
console.log(instance2.colors) // ['red', 'blue', 'green']

// 传参数
const SuperType = function (name) { this.name = name}
const SubType = function () { 
    SuperType.call(this, 'Tom')
    this.age = 20
}
const instance = new SubType()
console.log(instance.name) // Tom

盗用构造函数方法的问题: 1. 必须在构造函数中定义方法,导致函数不能重用。2. 子类不不能访问父类原型上的方法。所以盗用构造函数方法也不会单独使用。

3. 组合继承

综合了原型链和盗用构造函数, 思路是使用原型链继承原型上的属性和方法, 盗用构造函数继承实例属性。是 js 最常见的方式。
const SuperType = function (name) {
    this.name = name
    this.colors = ['red', 'blue']
}
SuperType.prototype.sayName = function () {
    console.log(this.name)
}
const SubType = function (name, age) {
    SuperType.call(this, name) // 盗用构造函数继承实例属性
    this.age = age
}
SubType.prototype = new SuperType() // 原型链继承原型上的属性和方法
SubType.prototype.sayAge = function () {
    console.log(this.age)
}

const instance1 = new SubType('tom', 19)
instance1.colors.push('green')
console.log(instance1.colors) // [ 'red', 'blue', 'green' ]
instance1.sayName() // tom
instance1.sayAge() // 19

const instance2 = new SubType('jerry', 12)
console.log(instance2.colors) // [ 'red', 'blue' ] 解决了引用类型数据问题
instance2.sayName() // jerry 共享方法
instance2.sayAge() // 12

4. 原型式继承

ES5 通过 Object.create()方法将 **原型式继承**规范了,这个方法接收两个参数:作为新对象原型的对象,以及给新对象定义额外属性的对象(第二个参数可以选)。
const person = {
    name: 'Tom',
    friends: ['jerry', 'Van', 'Court']
}

const anotherPeron = Object.create(person) // 第二个参数可选
anotherPeron.name = 'Greg'
anotherPeron.friends.push('Bob')

const yetAntherPerson = Object.create(person, { name: { value: 'Souct' }})
yetAntherPerson.friends.push('Sandy')

console.log(anotherPeron) // { name: 'Greg' }
console.log(yetAntherPerson.name) // 'Souct'

console.log(person) // { name: 'Tom', friends: [ 'jerry', 'Van', 'Court', 'Bob', 'Sandy' ] }

5. 寄生式继承

这是一种与原型式继承比较类似的方式, 思路类似于寄生构造函数和工厂模式: 创建一个实现继承的函数, 以某种方式增强对象,然后返回该对象。

6. 寄生式组合继承(引用类型继承的最佳模式)

/**
 * object函数创建一个临时的构造函数F
 * 将传入的对象赋值给这个构造函数的原型
 * 然后返回该构造函数的实例
 */
const object = function (o) {
    const F = function () {}
    F.prototype = o
    return new F()
}

/**
 * inheritPrototype() 函数实现了寄生式组合继承的核心逻辑
 * inheritPrototype函数接收两个参数: 子类构造函数 和 父类构造函数
 * inheritPrototype函数内部,第一步穿件父类原型的一个副本
 * 然后给prototype对象设置constructor属性,解决重写原型导致默认constructor丢失的问题
 * 最后将创建的新对象prototype赋值给子类的原型
 */
const inheritPrototype = function (subType, superType) {
    /**
     * 创建对象,可以不是object函数来创建
     * 只要按照这个模式, 传入对象,然后返回对象即可
     */
    const prototype = object(superType.prototype)
    prototype.constructor = subType // 增强对象
    subType.prototype = prototype // 赋值对象
}

const SuperType = function (name) {
    this.name = name
    this.colors = ['red', 'green']
}
SuperType.prototype.sayName = function () {
    console.log(this.name)
}
const SubType = function (name, age) {
    SuperType.call(this, name)
    this.age = age
}
SubType.prototype = new SuperType()
SubType.prototype.constructor = SubType

inheritPrototype(SubType, SuperType)

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

const p = new SubType('Tom', 19)
p.sayAge() // 19
console.log(p.colors) // [ 'red', 'green' ]
上一篇下一篇

猜你喜欢

热点阅读