JavaScript高级-实现继承

58 阅读5分钟

一、ES5中实现继承

1. 对象和函数的原型

  • 对象的原型
    • 每一个对象中都有一个特殊的内置属性[[prototype]],这个特殊的属性可以指向另一个对象
    • 获取的方式
      • 通过对象的__proto__属性可以获取到(存在兼容性问题)
      • 通过Object.getPrototypeOf方法可以获取到
    • 原型的作用
      • 当我们通过[[get]]方式获取一个属性对应的value时
      • 它会优先在自己的对象中查找,如果找到直接返回
      • 如果没有找到,那么会在原型对象中查找
  • 函数的原型prototype
    • 所有的函数都有一个prototype的属性
    • 将函数堪称一个普通的对象时,它具有__proto__(隐式原型)/作用:查找key对应的value时,会找到原型身上
    • 将函数堪称一个函数时,它是具备prototype(显式原型,对象没有的)/作用:用来构造对象时,给对象设置隐式原型的

2. new、constructor

  • new操作符
    • 创建一个空对象 --- 将构造函数的prototype赋值给空对象的__proto__
    • 显式原型中的属性
      • 构造函数中有一个prototype属性指向该构造函数的原型对象
      • 原型对象中有一个constructor属性指向该构造函数
    • 创建对象的内存表现
      function Person(name, age) {
          this.name = name
          this.age = age
      }
      Person.prototype.running = function() {
          console.log("running~")
      }
      var p1 = new Person("why", 18)
      var p2 = new Person("kobe", 30)
      
      console.log(p1.name)
      console.log(p2.name)
      p1.running()
      p2.running()
      
      34_ES5创建对象内存的表现.jpg
    • 新增属性
      // 新增属性
      Person.prototype.address = "中国"
      p1.___proto__.info = "中国很美丽"
      p1.height = 1.88
      p1.isAdmin = true
      // 获取属性
      console.log(p1.isAdmin)
      console.log(p2.isAdmin)
      console.log(p2.info)
      // 修改address
      p1.adderss = "广州市“
      
      34_ES5修改属性内存的表现.jpg
  • constructor属性
    • 默认情况下原型上都会添加一个属性叫做constructor,这个constructor指向当前的函数对象
  • 重写原型对象

3. 原型链的查找顺序

  • 原型链
    • 从一个对象中获取属性,如果在当前对象中没有获取到就会去它的原型上面获取
  • Object的原型
    • 原型链最顶层的原型对象就是Object的原型对象
    • Object直接创建出来的的原型对象是[Object: null prototype]{}
    • 该对象有原型属性,但是它的原型属性指向的是null,已经是顶层原型了
    • 该对象上有很多默认的属性和方法

4. 原型链实现的继承

  • 通过原型链实现继承
    // 1. 定义父类构造函数
    function Person(name, age) {
        this.name = name
        this.age = age
    }
    // 2. 父类原型上添加内容
    Person.prototype.running = function() {
        console.log("running~")
    }
     Person.prototype.eating = function() {
        console.log("eating~")
    }
    // 3. 定义子类构造函数
    function Student(name, age, sno, score) {
        this.name = name
        this.age = age
        this.score = score
    }
    // 4. 创建父类对象,并且作为子类的原型对象
    var p = new Person("why", 18)
    Student.prototype = p
    // 5. 在子类原型上添加内容
    Student.prototype.studying = function() {
        console.log(this.name, "studying")
    }
    var stu1 = new Student("why", 18, 111, 99)
    stu1.running()
    stu1.studying()
    
    34_父类创建一个实例对象作为子类的原型对象.jpg
  • 原型链继承的弊端
    • 某些属性是保存在p对象上的
    • 我们直接打印对象看不到这个属性
    • 这个属性会被多个对象共享,如果这个对象是一个引用类型,那么就会造成问题
    • 不能给Person传递参数,因为这个对象是一次性创建的

5. 借用构造函数继承

  • 借用构造函数继承
    • 在子类型构造函数的内部调用父类型构造函数
    • 通过apply()/call()方法在新创建的对象上执行构造函数
      function Student(name, friends, sno) {
        Person.call(this, name, friends)
        this.sno =sno
      }
      
  • 组合借用继承的问题
    • 无论在什么情况下,都会调用两次父类构造函数
    • 所有的子类实例事实上会拥有两份父类的属性

6. 原型式继承函数

  • 方法一
    function object(obj) {
      function Func() {}
      Func.prototype = obj
      return new Func()
    }
    
  • 方法二
    function object(obj) {
      var newObj = {}
      Object.setPrototypeOf(newObj, obj)
      return newObj
    }
    
  • 方法三
    var student = Object.create(person, {
      address: {
        value: "北京市",
        enumerable: true
      }
    })
    

7. 寄生组合实现继承

  • 寄生组合式继承
    • 创建一个封装继承过程的函数,该函数在内部以某种方式来增强对象,最后再将这个对象返回
    // 工具函数
    function createObject(o) {
       function F() {}
       F.prototype = o
       return new F()
    }
    
    // 寄生式函数
    // Subtype --- 子类  
    // Supertype --- 父类
    function inherit(Subtype, Supertype) {
       Subtype.prototype = createObject(Supertype.prototype)
         Object.defineProperty(Subtype.prototype, "constructor", {
        enumerable: false,
        configurable: true,
        writable: true,
        value: Subtype
       })
    }
    

二、JavaScript ES6中实现继承

1. 对象的方法补充

  • hasOwnProperty
    • 对象是否有一个属于自己的属性
  • in/for in操作符
    • 判断某个属性是否在某个对象或者对象的原型上
  • instanceof
    • 用于检测构造函数的prototype,是否出现在某个实例对象的原型链上
  • isPrototypeOf
    • 用于检测某个对象,是否出现在某个实例对象的原型链上

2. class方式定义类

  • 类与构造函数的异同
    • 类与构造函数的特性是一致的
  • 类的构造函数
    • 每个类都有一个自己的构造函数,这个方法的名称是固定的constructor

    • 每个类只能有一个构造函数,如果包含多个构造函数,那么会抛出异常

    • 当我们通过new关键字操作类时,会调用constructor,并且执行如下操作

      • 在内存中创建一个空对象
      • 这个对象内部的[[prototype]]属性会被赋值为该类的prototype属性
      • 构造函数内部的this,会指向创建出来的新对象
      • 执行构造函数的内部代码
      • 如果构造函数没有返回非空对象,则返回创建出来的新对象
  • 类的实例方法
    • 实例方法直接在类中定义
  • 类的访问器方法
  • 类的静态方法
    • 用static关键字来定义
    • 直接使用类来执行

3. extends实现继承

  • extends关键字实现继承
  • super关键字

4. Babel的ES6转ES5

5. 面向对象多态理解

  • javascript的多态
    • 不同的数据类型进行同一个操作,表现出不同的行为

6. ES6对象的增强

  • 属性的简写
  • 方法的简写
  • 对象属性名
  • 解构
    • 数组的解构
    • 对象的解构