继承和ES6的继承

128 阅读5分钟

认识继承

    // 构造函数1
    function Person(name) {
      this.name = name
    } 
    Person.prototype.init = function() {
      console.log('我是Person原型上的方法')
    }
    // 构造函数2
    function Stu(age) {
      this.age = age
    }
    Stu.prototype.sayHi = function() {
      console.log('你好')
    }

    const s1 = new Stu(18)
    console.log(s1)
    console.log(s1.__proto__)
    /**
     * 如果s1对象内部 具有一个属性为name
     * 并且s1还可以使用init方法
     *    那么 我们就可以说Stu这个构造函数 继承自Person构造函数
     *        Stu 是 Person 的子类
     *        Person 是 Stu 的父类
     **/

原型继承

  • 利用自定义原型的方式实现继承关系
  • 核心: 将子类的原型修改为父类的实例化对象
  • 优点: 可以使用父类的属性和方法,实现了继承
  • 缺点:
      1. 原本原型上的方法不能用了(因为 原型对象被改变了)
      1. 继承到的属性并不在自己身上,而是在原型对象上(不过不影响使用)
    // 构造函数1
    function Person(name) {
      this.name = name
    } 
    Person.prototype.init = function() {
      console.log('我是Person原型上的方法')
    }
    // 构造函数2
    function Stu(age) {
      this.age = age
    }
    // Stu.prototype = {
    //   a: 1,
    //   b: 2
    // }
    Stu.prototype = new Person('张三')
    Stu.prototype.sayHi = function() {
      console.log('你好')
    }
    console.log('Stu原型对象', Stu.prototype)
    const s1 = new Stu(18) 
    console.log('Stu 实例化对象:', s1)
    console.log(s1)
    // console.log(s1.name)
    /**
     * 访问对象s1的name属性
     *    1. 先在对象本身查找,找到就直用,但是自身就只有一个age属性,所以没找到
     *    2. 所以会去自己的__proto__对象中查找,也就是自己构造函数的原型对象
     *        2.1 但是现在 自己构造函数的原型对象 已经被我们修改为Person这个构造函数的实例化对象
     *        2.2 这个实例化对象上是具有name属性的,并且还有一个 init方法
     *    3. 所在实例化对象中找到了name属性,值为:张三
     * */ 
    // s1.init()
    s1.sayHi()

借用构造函数继承

  • 核心: 把父亲构造函数当做普通函数调用,利用call修改这个函数内部的this指向(如果不修改的话,函数的this指向了其他的对象)
  • 优点: 把父类的属性全都继承在了自己身上
  • 缺点:
      1. 只能继承父类的属性,不能继承父类的方法
      1. 每次调用Stu的时候,Stu内部还会自动调用一次Person函数
    // 构造函数1
    function Person(name) {
      this.name = name
    } 
    Person.prototype.init = function() {
      console.log('我是Person原型上的方法')
    }
    // 构造函数2
    function Stu(age, name) {
      // 1. 自动创建出来一个对象(这个函数内部的 this 就指向了这个对象,所以你可以通过 this向着个对象上添加属性)
      // 2. 手动向对象上添加属性
      this.age = age
      // 将Person内部的this 修改为了第一步被自动创建出来的对象,并传递一个参数name给Person 函数使用
      Person.call(this, name)
      // 3. 自动返回这个对象
    }
    Stu.prototype.sayHi = function() {
      console.log('你好')
    }    
    const s1 = new Stu(18, '张三')
    console.log(s1)
    s1.init() //报错,无法继承方法
    /**
     * 分析 
     *    1. const s1 = new Stu(18, '张三') 通过new 关键字 调用Stu这个构造函数,得到一个实例化对象,存储在了常量s1内部
     * 
     *    调用Stu函数时发生的事情
     *        1. 给对象上添加一个age属性,并将形参的值 赋值给它
     *        2. 调用Person并通过 call方法 改变了这个Person这个函数内部的this指向
     *            2.1 所以现在Person函数内部的this 就相当于是 Stu 构造函数内部被自动创建出来的对象
     *                调用Person函数是发生的事情
     *                    this.name = 形参
     *        3. 代码执行完毕之后,new Stu 时内部被自动创建出来的对象被添加了两个属性
     *            3.1 是在Stu函数内部添加的age属性
     *            3.2 是在Person 函数内部添加的name属性
     * */ 

组合继承

  • 核心: 把原型继承与借用构造函数继承结合起来使用
  • 优点: 实例化对象上具有继承到的属性,并且能够继承到父类原型上的方法
  • 缺点: 实例化对象上与原型对象上,都有父类的属性(多了一套属性,但是并不影响使用)
    // 构造函数1
    function Person(name) {
      this.name = name
    } 
    Person.prototype.init = function() {
      console.log('我是Person原型上的方法')
    }
    // 构造函数2
    function Stu(age, name) {
      this.age = age
      // 1. 借用构造函数继承,得到父类的属性(放在了 对象上,并且没有继承父类原型上的方法)
        Person.call(this, name)
    }
    // 2. 利用原型继承,得到父类的属性(原型上)与方法
    Stu.prototype = new Person('这个字符串没有意义')
    Stu.prototype.sayHi = function() {
      console.log('你好')
    } 
    
    // console.log(Person.prototype)
    // const s2 = new Person()
    // console.log(s2.init)
    // 创建实例化对象
    const s1 = new Stu(18, '张三')
    console.log('Stu实例化对象', s1)
    // console.log('Stu原始对象', Stu.prototype)
    // console.log(s1.name)
    console.log(Stu.prototype)
    console.log(s1.__proto__)
    // s1.init()
    /**
     * 访问s1对象的name属性
     *    1. 去对象自身内部查找,现在找到了,直接使用,并且停止查找
     * */ 

拷贝继承

  • 补充: for...in遍历,可以遍历到对象的原型上的方法
    // 构造函数1
    function Person(name) {
      this.name = name
    } 
    Person.prototype.init = function() {
      console.log('我是Person原型上的方法')
    }
    // const p1 = new Person('张三')
    // for(let key in p1) {
    //   console.log(key, p1[key])
    // }
    // 构造函数2
    function Stu(age, name) {
      this.age = age

      /**
       * 在 子类构造函数实例化父类构造函数,得到父类构造函数前实例化对象
       * 然后利用 for...in 可以遍历到原型上的属性这个特点,将实例化对象的属性与其原型上的方法
       * 一起拷贝到子类构造函数的原型中
       * */ 
      const p1 = new Person('张三')
      // 拷贝循环
      for(let key in p1) {
        // console.log(key, p1[key])
        Stu.prototype[key] = p1[key]
      }
    }
    Stu.prototype.sayHi = function() {
      console.log('你好')
    } 
    // 创建实例化对象
    const s1 = new Stu(18, '张三')

    console.log('Stu 实例化对象', s1)
    console.log('Stu 原型对象', s1.__proto__)
    // console.log(Stu.prototype)

ES6类的继承

  • 语法要求:
      1. 书写了类的时候:class 子类类名 extends 父类类名 {...}
      1. 书写 constructor 的时候: 内部需要书写super('父类需要的参数')
  • 注意:
      1. extends 和 super 必须同时出现才能完成继承
      1. super 必须出现在constructor 的第一行
  • 额外扩展:ES6 类也能继承ES5的构造函数
  • 验证方法: 将Person ES6类的写法更改为ES5的构造函数写法即可
    // 父类
    class Person {
      constructor(name) {
        this.name = name
      }
      init() {
        console.log('我是 Person 原型上的方法')
      }
    }

    // 子类
    class Stu extends Person {
      constructor(age) {
        super('父类需要的参数,都写在这里边') //必须写在第一行
        this.age = age
      }
      sayHi() {
        console.log('你好~~~')
      }
    }
    const s1 = new Stu(18)
    console.log(s1)
    console.log(s1.name)
    s1.init()