Skip to content

8.js的原型、原型链、new关键字、继承问题

原型

js
  function Person() {}
  • Person是一个构造函数,每个构造函数都会生成一个prototype属性,指向一个空对象,这个空对象就是原型。每一个实例对象都会从原型继承属性和方法。
  • 原型对象中有一个属性constructor,它指向构造函数

显/隐式原型

  • 显式原型:每个函数都有一个prototype,即显式原型
  • 隐式原型:每个实例(new 构造函数)都有一个[[prototype]](_proto_),即隐式原型
  • 隐式原型的值(_proto_)为对应构造函数显式原型(prototype)的值

原型链

js
  function Person(){}
  // 等价于
  const Person = new Function()

Person.prototype是一个对象,对象可以理解为Object的实例,所以prototype__proto__属性指向Object.prototype

继承

继承的目的就是重复使用另外一个对象的属性和方法

原型链继承

让一个构造函数的原型是另一个类型的实例,那么这个构造函数new出来的实例就具有该实例的属性。 当试图访问一个对象的属性时,它不仅仅在该对象上搜寻,还会搜寻该对象的原型,以及该对象的原型的原型,依次层层向上搜索,直到找到一个名字匹配的属性或到达原型链的末尾。

js
  function Parent() {
    this.isshow = true
    this.info = {
        name: 'likewei',
        age: 28
    }
  }

  Parent.prototype.getInfo = function() {
   console.log(this.info);
   console.log(this.isShow);
  }

  function Child(){}

  Child.prototype = new Parent()
  
  let c = new Child();
  c.getInfo()

问题

  • 问题一: 原型中包含引用值时会在所有实例间共享
    js
      function superType() {
        this.colors = ["red", "blue", "green"]; 
      }
      function subType() {}
      subType.prototype = new superType();
      let instance1 = new subType()
      instance1.colors.push("black"); 
      console.log(instance1.colors); // "red,blue,green,black" 
      let instance2 = new SubType(); 
      console.log(instance2.colors); // "red,blue,green,black"
  • 问题二: 子类型实例化的时候不能给父类型的构造函数传参

借用构造函数继承(对象伪装、经典继承)

为了解决原型包含引用值导致的继承问题,盗用构造函数(对象伪装、经典继承)开始流行。 基本思路是: 在子类构造函数中调用父类构造函数 因为函数就是在特定上下文中执行代码的简单对象,所以可以使用apply()或call()方法以新创建的对象为上下文执行构造函数

js
    function SuperType() { 
      this.colors = ["red", "blue", "green"]; 
    } 
    function SubType() { 
      // 继承 SuperType 
      SuperType.call(this); 
    } 
    let instance1 = new SubType(); 
    instance1.colors.push("black"); 
    console.log(instance1.colors); // "red,blue,green,black" 
    let instance2 = new SubType(); 
    console.log(instance2.colors); // "red,blue,green"

问题

  • 必须在构造函数中定义方法,通过盗用构造函数继承的方法本质上都变成了实例自己的方法,不是公共的方法,因此失去了复用性。

  • 子类不能访问父类原型上的方法

    js
      function Person(eyes) {
        this.eyes = eyes
        this.getEyes = function() { 
          return this.eyes
        }
      }
    
      Person.prototype.returnEyes = function() {
        return this.eyes
      }
    
      function YellowRace() {
        Person.call(this, 'yellow')
      }
    
      const hjy = new YellowRace()
      console.log(hjy.getEyes()) // yellow
      console.log(hjy.returnEyes()) // TypeError: hjy.returnEyes is not a function

组合继承

组合继承结合了原型链继承和盗用构造函数继承,综合了他们两者的优点,取其精华,去其糟粕

js
  function Father() {
    this.colors = ['white', 'black', 'blue']
  }

  Father.prototype.getColor = function() {
    return this.colors
  }

  function Son() {
    Father.call(this)
  }

  Son.prototype = new Father()
  
  const s1 = new Son()
  s1.colors.push('green')
  console.log(s1.colors) // ['white', 'black', 'blue', 'green']
  const s2 = new Son()
  console.log(s2.colors) // ['white', 'black', 'blue']
  s2.getColor() // ['white', 'black', 'blue']

问题

调用了两次构造函数,浪费性能

原型式继承 - 工厂模式(Object.create)

核心代码:

js
  function object(f) {
    function F {}
    F.prototype = f
    return new F()
  }

上述代码相当于精简版Object.create

问题

  • 原对象中引用类型的属性会被新对象共享

寄生式继承

在原型式继承的基础上增强对象(给对象挂载新属性、方法)

js
  function extend(o) {
    let clone = Object.create(o);
    clone.age = 20
    clone.sayHi = function(){
      console.log('Hello')
    }
    return clone
  }

问题

  • 函数难重用

寄生组合式继承(最佳)

是组合继承(原型链、盗用构造函数)和寄生式继承的结合

解决了组合继承中,构造函数调用两次的问题(不通过调用父类构造函数给子类赋值(不使用Son.prototype = new Father()),而使用寄生式继承来继承父类原型,然后将返回的新对象赋值给子类原型)

js
  // 实现寄生式组合继承的核心代码,接受两个参数(子类/父类构造函数)
  // 第一步: 创建父类原型的一个副本
  // 第二步: 给返回的prototype对象设置constructor属性,解决重写原型导致默认constructor丢失的问题
  // 第三步: 将新创建的对象赋值给子类型的原型
  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(this.name); 
  }; 
  function SubType(name, age) { 
    SuperType.call(this, name); 
    this.age = age; 
  } 
  inheritPrototype(SubType, SuperType); 
  SubType.prototype.sayAge = function() { 
    console.log(this.age); 
  };

ES6 CLASS继承

本质是原型继承

KESHAOYE-知识星球