Js几种继承方式

69 阅读4分钟

1、原型链继承

让一个构造函数的原型是另一个构造函数的实例,那么这个构造函数new出来的实例就具有另一个实例的属性

    // 父类构造函数
    function Parent() {
        this.info = {
            name: '摩拉克斯',
            age: 5000,
            skill: '元素战技',
            break: '元素爆发'
        }
    }
    
    Parent.prototype.say = function () {
        console.log(this.info)
    }
    // 子类构造函数
    function Child() {}
    // 将子类的原型指向父类的实例对象(原型链继承)
    Child.prototype = new Parent()
    
    // 实例化子类,发现子类本身是没有info属性和say方法的,但是可以读和写
    let xiao = new Child()
    xiao.info.name = '魈'
    xiao.info.sex = '男'
    xiao.say() // {name: '魈', age: 5000, skill: '元素战技', break: '元素爆发', sex: '男'}
    
    // 但是当一个子类实例继承的属性方法更改,其他实例化的对象属性也会更改
    let wangxiaomei =  new Child()
    wangxiaomei.info.sex = '女'
    wangxiaomei.say() // {name: '魈', age: 5000, skill: '元素战技', break: '元素爆发', sex: '女'}
    xiao.say() // {name: '魈', age: 5000, skill: '元素战技', break: '元素爆发', sex: '女'}
    

当试图访问一个对象的属性时,它不仅仅在该对象上搜寻,还会搜寻该对象的原型,以及该对象的原型的原型,依次层层向上搜索,直到找到一个名字匹配的属性或到达原型链的末尾。

优点:写法方便简洁,容易理解。 缺点:对象实例共享所有继承的属性和方法。

2、借用构造函数继承

在子类型构造函数的内部调用父类型构造函数;使用 apply() 或 call() 方法将父对象的构造函数绑定在子对象上。

    // 父类构造函数
    function Parent({name, sex}) {
        this.info = {
            name: name,
            age: 5000,
            sex: sex
        }
    }
    
    // 子类构造函数
    function Child(obj) {
        // 将父类构造函数指向子类的this
        Parent.call(this, obj)
    }
    
    
    let xiao = new Child({name: '魈', sex: '男'})
    xiao.info.age = 2000
    console.log(xiao.info) // {name: '魈', age: 2000, sex: '男'}
    
    let wangxiaomei = new Child({name: '王小美', sex: '女'})
    wangxiaomei.info.age = 3000
    console.log(wangxiaomei.info) // {name: '王小美', age: 3000, sex: '女'}
    console.log(xiao.info) // {name: '魈', age: 2000, sex: '男'}

优点:解决了原型链实现继承的不能传参的问题和父类的原型共享的问题。 缺点:借用构造函数的缺点是方法都在构造函数中定义,因此无法实现函数复用。在父类型的原型中定义的方法,对子类型而言也是不可见的,结果所有类型都只能使用构造函数模式。

3、组合式继承

是前两种继承的结合,使用原型链实现对原型属性和方法的继承,而通过借用构造函数来实现对实例属性的继承。这样,既通过在原型上定义方法实现了函数复用,又能够保证每个实例都有自己的属性

    // 父类构造函数
    function Parent({name, sex}) {
        this.info = {
            name: name,
            age: 5000,
            sex: sex
        }
    }
    Parent.prototype.say = function () {
        console.log(this.info)
    }
    
    // 子类构造函数
    function Child(obj) {
        // 将父类构造函数指向子类的this
        Parent.call(this, obj)
    }
    
    // 将子类的原型指向父类的实例
    Child.prototype = new Parent({name: ''sex:''})
    
    // 既可以获取父类的属性,又可以调用父类的方法
    let xiao = new Child({name: '魈', sex: '男'})
    xiao.info.age = 2000
    xiao.say() // {name: '魈', age: 2000, sex: '男'}
    
    let wangxiaomei = new Child({name: '王小美', sex: '女'})
    wangxiaomei.info.age = 3000
    wangxiaomei.say() // {name: '王小美', age: 2000, sex: '女'}
    xiao.say() // {name: '魈', age: 2000, sex: '男'}

优点就是解决了原型链继承和借用构造函数继承造成的影响。 缺点是无论在什么情况下,都会调用两次超类型构造函数:一次是在创建子类型原型的时候,另一次是在子类型构造函数内部

4、寄生组合式继承

减少组合继承的父类构造函数的一次调用,不直接调用父类构造函数给子类原型赋值,而是通过创建空函数 Fun 获取父类原型的副本

   // 父类构造函数
   function Parent({name, sex}) {
        this.info = {
            name: name,
            age: 5000,
            sex: sex
        }
        this.skill = '拒收病婿'
    }
    // 给父类添加方法
    Parent.prototype.say = function () {
        console.log(this.info)
    }
    
    // 子类构造函数
    function Child(obj) {
        // 将父类构造函数指向子类的this
        Parent.call(this, obj)
        this.skill = obj.skill
    }
    
    // 创建父类副本
    function objectCopy(obj) {
      function Fun() { }
      Fun.prototype = obj
      return new Fun()
    }
    
    // 创建副本,并且将子类的原型指向副本的实例
    function inheritPrototype(child, parent) { 
        let prototype = objectCopy(parent.prototype)
        prototype.constructor = child
        Child.prototype = prototype
    }
    
    // 调用,继承
    inheritPrototype(Child, Parent)
    // 给子类添加方法
    Child.prototype.saySkill = function () {
        console.log(this.skill)
    }
    
    let xiao = new Child({name: '魈'sex: '男', skill: '靖妖傩舞' })
    console.log(xiao)
    xiao.say() // {name: '魈', age: 5000, sex: '男'}
    xiao.saySkill() // 靖妖傩舞
    
    

注:自己学习记忆 学习自一位博主的文章