js的一些继承方式

137 阅读6分钟

js继承方式

方式1:原型链继承

核心:将父类的实例作为子类的原型

  • 优点:
    • 1、非常纯粹的继承关系,实例是子类的实例,也是父类的实例
    • 2、父类新增原型方法/属性,子类都能访问到
  • 缺点:
    • 1、创建⼦类实例的时候,不能传⽗类的参数(⽐如name)。
    • 2、⼦类实例共享了⽗类构造函数的引⽤属性,⽐如arr属性。
    • 4、不支持多继承
    function Parent(name) {
        this.name = name || '父亲' //实例基本属性(该属性强调私有,不共享)
        this.arr=[1] //该属性强调私有
    }
    Parent.prototype.say = function() { //将要复用,共享的方法定义在父类原型上
        console.log('hello')
    }
    function Child(like){
        this.like=like
    }
    Child.prototype=new Parent()
    Child.prototype.constructor=Child//修正constructor指向
    let boy1=new Child()
    let boy2=new Child()
    //优点:共享了⽗类构造函数的say⽅法
    console.log(boy1.say(),boy2.say(),boy1.say===boy2.say);//hello,hello,true
    //缺点1:不能向⽗类构造函数传参
    console.log(boy1.name,boy2.name,boy1.name===boy2.name);//⽗亲,⽗亲,true
    //缺点2:⼦类实例共享了⽗类构造函数的引⽤属性,⽐如arr属性
    boy1.arr.push(2)
    //修改了boy1的arr属性,boy2的arr属性,也会变化,
    //因为两个实例的原型上(Child.prototype)有了⽗类构造函数的实例属性arr;
    //所以只要修改了boy1.arr,boy2.arr的属性也会变化。
    console.log(boy2.arr);//[1,2]
    
    //注意1:修改boy1的name属性,是不会影响到boy2.name。因为设置boy1.name相当于在⼦类实例新增了name属性。
    //注意2:
    console.log(boy1.constructor);//Parent你会发现实例的构造函数居然是Parent。⽽实际上,我们希望⼦类实例的构造函数是Child,所以要记得修复构造函数指向。
    //修复如下:Child.prototype.constructor=Child;
    

方式2:构造继承

  • 核⼼:借⽤⽗类的构造函数来增强⼦类实例,等于是复制⽗类的实例属性给⼦类。
  • 优点:实例之间独⽴。
    • 创建⼦类实例,可以向⽗类构造函数传参数。
    • ⼦类实例不共享⽗类构造函数的引⽤属性。如arr属性
    • 可实现多继承(通过多个call或者apply继承多个⽗类)
  • 缺点:
    • ⽗类的⽅法不能复⽤ 由于⽅法在⽗构造函数中定义,导致⽅法不能复⽤(因为每次创建⼦类实例都要创建⼀遍⽅法)。⽐如say⽅法。(⽅法应该要复⽤、共享)
    • ⼦类实例,继承不了⽗类原型上的属性。(因为没有⽤到原型)
    function Parent(name) {
        this.name = name //实例基本属性(该属性强调私有,不共享)
        this.arr=[1] //该属性强调私有
        this.say =function(){ //实例引⽤属性(该属性,强调复⽤,需要共享)
            console.log('hello')
        }
    }
  
    function Child(name,like){
        Parent.call(this,name) //核⼼ 拷⻉了⽗类的实例属性和⽅法
        this.like=like
    }
    let boy1 = new Child('小A','苹果')
    let boy2 = new Child('小B','香蕉')
    //优点1:可向⽗类构造函数传参
    console.log(boy1.name,boy2.name); //⼩A,⼩B
    //优点2:不共享⽗类构造函数的引⽤属性
    boy1.arr.push(2)
    console.log(boy1.arr,boy2.arr); //[1,2][1]
    //缺点1:⽅法不能复⽤
    console.log(boy1.say===boy2.say) //false(说明,boy1和boy2的say⽅法是独⽴,不是共享的)
    //缺点2:不能继承⽗类原型上的⽅法
    Parent.prototype.walk=function(){ //在⽗类的原型对象上定义⼀个walk⽅法。
        console.log('我会⾛路')
    }
    boy1.walk; //undefined(说明实例,不能获得⽗类原型上的⽅法)

方式3:组合继承

  • 核⼼:通过调⽤⽗类构造函数,继承⽗类的属性并保留传参的优点;然后通过将⽗类实例作为⼦类原型,实现函数复⽤。
  • 优点:
    • 保留构造函数的优点:创建⼦类实例,可以向⽗类构造函数传参数。
    • 保留原型链的优点:⽗类的⽅法定义在⽗类的原型对象上,可以实现⽅法复⽤。
    • 不共享⽗类的引⽤属性。⽐如arr属性
  • 缺点:
    • 由于调⽤了2次⽗类的构造⽅法,会存在⼀份多余的⽗类实例属性。
  • 注意:'组合继承'这种⽅式,要记得修复Child.prototype.constructor指向 第⼀次Parent.call(this);从⽗类拷⻉⼀份⽗类实例属性,作为⼦类的实例属性,第⼆次Child.prototype=newParent();创建⽗类实例作为⼦类原型,Child.protype中的⽗类属性和⽅法会被第⼀次拷⻉来的实例属性屏蔽掉,所以多余。
    function Parent(name) {
        this.name = name //实例基本属性(该属性强调私有,不共享)
        this.arr=[1] //该属性强调私有
    }
    Parent.prototype.say=function(){ //---将需要复⽤、共享的⽅法定义在⽗类原型上
        console.log('hello')
    }
    function Child(name,like){
        Parent.call(this,name,like) //核⼼ 第⼆次
        this.like=like
    }
    Child.prototype=new Parent() //核⼼ 第⼀次
    Child.prototype.constructor=Child //修正constructor指向
    
    let boy1 = new Child('小A','苹果')
    let boy2 = new Child('小B','香蕉')
    
    //优点1:可向⽗类构造函数传参
    console.log(boy1.name,boy1.like); //⼩A,苹果
    //优点2:可复⽤⽗类原型上的⽅法
    console.log(boy1.say===boy2.say)//true
    //优点3:不共享⽗类的引⽤属性,如arr属性
    boy1.arr.push(2)
    console.log(boy1.arr,boy2.arr);//[1,2][1]可以看出没有共享arr属性。
    //缺点1:由于调⽤了2次⽗类的构造⽅法,会存在⼀份多余的⽗类实例属性
    

Child.prototype=new Parent() console.log(Child.prototype.proto===Parent.prototype);//true 因为Child.prototype等于Parent的实例,所以__proto__指向Parent.prototype

  • 为什么‘组合继承’这种方式,会执行两次父类构造函数?
    • 第一次 Child.prototype=new Parent() new的过程’的第三步,其实就是执⾏了⽗类构造函数。
    • 第⼆次:Parent.call(this,name,like) call的作⽤是改变函数执⾏时的上下⽂。⽐如:A.call(B)。其实,最终执⾏的还是A函数,只不过是⽤B来调⽤⽽已。所以,你就懂了Parent.call(this,name,like),也就是执⾏了⽗类构造函数Person。

方式4:寄⽣组合继承

  • 优点:完美
    function Parent(name) {
        this.name = name //实例基本属性(该属性强调私有,不共享)
        this.arr=[1] //该属性强调私有
    }
    Parent.prototype.say=function(){ //---将需要复⽤、共享的⽅法定义在⽗类原型上
        console.log('hello')
    }
    function Child(name,like){
        Parent.call(this,name,like) //核⼼
        this.like=like
    }
    //核⼼通过创建中间对象,⼦类原型和⽗类原型,就会隔离开。不是同⼀个啦,有效避免了⽅式4的缺点。
    Child.prototype=Object.create(Parent.prototype)
    Child.prototype.constructor=Child
    
    let boy1 = new Child('小A','苹果')
    let boy2 = new Child('小B','香蕉')
    
  • Object.create(object,propertiesObject) Object.create()⽅法创建⼀个新对象,使⽤第⼀个参数来提供新创建对象的__proto__(以第⼀个参数作为新对象的构造函数的原型对象);⽅法还有第⼆个可选参数,是添加到新创建对象的属性,例如:
    const a = Object.create(Person.prototype,{
        age:{
            value:12,
            writable:true,
            configurable:true
            }
   })
  • new与Object.create()的区别
    • new产⽣的实例,优先获取构造函数上的属性;构造函数上没有对应的属性,才会去原型上查找;如果构造函数中以及原型中都没有对应的属性,就会报错。
    • Object.create()产⽣的对象,只会在原型上进⾏查找属性,原型上没有对应的属性,就会报错。

方式5:实例继承

  • 核心:为父类实例添加新特性,作为子类实例返回
 function Parent(name) {
        this.name = name //实例基本属性(该属性强调私有,不共享)
        this.arr=[1] //该属性强调私有
    }
    Parent.prototype.say=function(){ //---将需要复⽤、共享的⽅法定义在⽗类原型上
        console.log('hello')
    }

  function Child(name){ 
      var instance = new Parent()
      instance.name = name
      return instance
  }
  • 缺点:
    • 实例是父类的实例,不是子类的实例
    • 不支持多继承

方式6:ES6继承

    class Person {
        constructor(name) {
          this.name =name
        }

        sayName() {
          console.log(this.name)
        }
      }
      class Student extends Person {
        constructor(name) {
          super(name)
        }
      }
      let person = new Person('oo')
      let student = new Student('ss')
      person.sayName()
      student.sayName()