js里继承的实现方法及优缺点

322 阅读4分钟

1. 原型链

  • 简介:基本思想就是利用原型让一个引用类型继承另一个引用类型的属性和方法
  • 示例代码:
      function person() {
        this.name = 'tom'
        this.likeColors = ['red', 'blue']
      }
    
      person.prototype.getName = function () {
        return this.name
      }
    
      function student() {
        // 一些学生的特有属性
        // ...
      }
    
      //继承了 person
      student.prototype = new person()
    
      let wright = new student()
      let anna = new student()
      console.log(wright.likeColors) //['red', 'blue']
      console.log(anna.likeColors)  //['red', 'blue']
      console.log(' ')
    
      wright.likeColors.push('white')
      console.log(wright.likeColors) //["red", "blue", "white"]
      console.log(anna.likeColors)   //["red", "blue", "white"]
    
  • 优点:-
  • 缺点:
    1. 包含引用类型值的原型属性会被所有实例共享,修改一个实例的引用类型值得原型属性时会影响所有的实例
    2. 没有办法在补影响所有对象实例的情况下,给父类型的构造函数传递参数

2. 借用构造函数(也叫伪造对象或经典继承)

  • 简介:在子类型的构造函数内部调用父类型的构造函数

  • 示例代码:

    function SuperType() {
        this.colors = ['red', 'blue', 'green']
      }
    
      function SubType() {
        //继承了 SuperType
        SuperType.call(this)
      }
    
      let instance1 = new SubType()
      instance1.colors.push('black')
      alert(instance1.colors)   // red, blue, green, black
    
      let instance2 = new SubType()
      alert(instance2.colors)   // red, blue, green
    
  • 优点:

    1. 避免了引用类型的属性被所有实例共享
    2. 可以在子类型构造函数中向父类型构造函数传递参数
  • 缺点:

    1. 方法都是在构造函数中定义,不利于函数复用(每次创建实例都会创建一遍方法)
    2. 超类原型中定义的方法,对子类而言是不可见的,

3. 组合继承(最常用)

  • 简介:将原型链和构造函数组合到一起(使用原型链实现对原型属性和方法的继承,通过构造函数实现对实例属性的继承)

  • 示例代码:

    function SuperType(name) {
      this.name = name
      this.colors = ['red', 'blue', 'green']
    }
    
    SuperType.prototype.sayName = function () {
      alert(this.name)
    }
    
    function SubType(name, age) {
      //继承属性
      SuperType.call(this, name)   //第二次调用父类的构造方法 
      this.age = age
    }
    
    //继承方法
    SubType.prototype = new SuperType()
    SubType.prototype.constructor = SubType
    SubType.prototype.sayAge = function () {
      alert(this.age)
    }
    
    let instance1 = new SubType('Nicholas', 29) //第一次调用父类的构造方法
    instance1.colors.push('black')
    console.log(instance1.colors) //'red,blue,green,black'
    instance1.sayName() //'Nicholas'
    instance1.sayAge() //29
    
    let instance2 = new SubType('Greg', 27)
    console.log(instance2.colors) //'red,blue,green'
    instance2.sayName() //'Greg'
    instance2.sayAge() //27
    
  • 优点:

    1. 组合继承避免了原型链和借用构造函数的缺陷,融合了它们的优点
    2. instanceof 和 isPrototypeOf()也能够用于识别基于组合继承创建的对象。
  • 缺点:

    1. 无论什么情况下,都会调用两次父类型构造函数:一次是在创建子类型原型的时候,另一次是在子类型构造函数内部
    2. 子类型最终会包含父类型对象的全部实例属性,但我们不得不在调用子类型构造函数时重写这些属性

4. 原型式继承

  • 简介:借助原型可以基于已有的对象创建新对象,同时还不必因此创建自定义类型
  • 示例代码:
    function object(o) {
    function F() {
    }
    
    F.prototype = o
    return new F()
    }
    
    let person = {
    name: 'Nicholas',
    friends: ['Shelby', 'Court', 'Van']
    }
    let onePerson = object(person)
    onePerson.name = 'Greg'
    onePerson.friends.push('Rob')
    
    let anotherPerson = object(person)
    anotherPerson.name = 'Linda'
    anotherPerson.friends.push('Barbie')
    
    console.log(onePerson)  //{name: "Greg"}
    console.log(anotherPerson)  //{name: "Linda"}
    console.log(onePerson.friends)  //'Shelby,Court,Van,Rob,Barbie'
    console.log(anotherPerson.friends)  //'Shelby,Court,Van,Rob,Barbie'
    console.log(person.friends)  //'Shelby,Court,Van,Rob,Barbie'
    
  • 优点:
    1. 在没有必要兴师动众地创建构造函数,而只想让一个对象与另一个对象保持类似的情况下很适用
  • 缺点:
    1. 必须有一个对象可以作为另一个对象的基础
    2. 包含引用类型值的原型属性会被所有实例共享(这点和原型链继承一样)

5. 寄生式继承

  • 简介:创建一个仅用于封装继承过程的函数,该函数在内部以某种方式来增强对象,最后返回对象(可以理解为在原型式继承的基础上新增一些函数或属性)
  • 示例代码:
    function object(o) {
      function F() {
      }
    
      F.prototype = o
      return new F()
    }
    
    
    function createAnother(original) {
      let clone = object(original)  //通过调用函数创建一个新对象
      clone.sayHi = function () {  //以某种方式来增强这个对象
        console.log('hi')
      }
      return clone  //返回这个对象
    }
    
    let person = {
      name: 'Nicholas',
      friends: ['Shelby', 'Court', 'Van']
    }
    let anotherPerson = createAnother(person)
    
    anotherPerson.sayHi()  //hi
    console.log(anotherPerson.name)  //Nicholas
    console.log(anotherPerson.friends)  //Shelby, Court, Van
    
    // 内省
    console.log(person.isPrototypeOf(anotherPerson))  //true
    console.log(Object.getPrototypeOf(anotherPerson) === person)  //true
    console.log(anotherPerson.__proto__ === person)  //true
    
  • 优点:
    1. 在主要考虑对象而不是自定义类型和构造函数的情况下,寄生式继承也是一种有用的模式
  • 缺点:
    1. 使用寄生式继承来为对象添加函数,会由于不能做到函数复用而降低效率

6. 寄生组合式继承(是实现基于类型继承的最有效方式)

  • 简介:通过构造函数继承属性,通过原型链混成形式继承方法(不必为了指定子类型的原型而调用父类型的构造函数,我们所需要的只是父类型的原型的一个副本,本质上就是使用寄生式继承来继承父类型的原型,然后再将结果指定给子类型的原型)
  • 示例代码:
      function object(o) {
        function F() {
        }
    
        F.prototype = o
        return new F()
      }
    
      function inheritPrototype(subType, superType) {
        let prototype = object(superType.prototype) //创建对象
        prototype.constructor = subType //增强对象
        subType.prototype = prototype //指定对象
      }
    
      function SuperType(name) {
        this.name = name
        this.colors = ['red', 'blue', 'green']
      }
    
      SuperType.prototype.sayName = function () {
        console.log('name: ', this.name)
      }
    
      function SubType(name, age) {
        SuperType.call(this, name)
        this.age = age
      }
    
      inheritPrototype(SubType, SuperType)
      SubType.prototype.sayAge = function () {
        console.log('age: ', this.age)
      }
      let wright = new SubType('wright', 18)
    
      console.log(wright.colors) //["red", "blue", "green"]
      console.log(wright.name)   //wright
      wright.sayName()           //name:  wright
      console.log(wright.age)    //18
      wright.sayAge()            //age: 18
    
      //内省
      console.log(SubType.prototype.isPrototypeOf(wright)) //true
      console.log(SubType.prototype.isPrototypeOf(wright))  //true
      console.log(SuperType.prototype.isPrototypeOf(SubType.prototype)) //true
    
  • 优点:
    1. 只调用一次父类的构造函数,因此避免了在子类的prototype上创建不必要的、多余的属性
    2. 原型链保持不变
    3. 能够正常使用instance 和 prototypeOf()
  • 缺点:-