美文网首页
继承(Javascript)

继承(Javascript)

作者: 东方三篇 | 来源:发表于2021-11-29 11:20 被阅读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' ]
    

    相关文章

      网友评论

          本文标题:继承(Javascript)

          本文链接:https://www.haomeiwen.com/subject/ylakxrtx.html