JS继承

105 阅读8分钟

认识继承

  • 如果 想让这个s这个对象内部有一个name属性,并且可以使用sayName这个方法
  • 就可以通过继承的方式让Stu这个类,继承上Person这个类内部的属性与原型上的方法
  • function Person(name) {
                this.name = name
            }
            Person.prototype.sayName = () => {
                console.log('name')
            }
    ​
            function Stu(age) {
                this.age = age
            }
             const s = new Stu(18)
    ​
            console.log(s)  // {age: 18}
    

原型继承

  • 利用自定义原型的方式去继承

  • 当Stu这个类继承了 Person 这个类的时候

  • 我们把 Person 叫做 Stu 的父类

  • 把 Stu 叫做 Person 的 子类

  • 核心: 就子类的原型 指向 父类的 实例化对象

  • 优点: 实现了 继承

  • 缺点:

    继承了属性, 但是不在自己身上

    失去了自己的原型

  • // 1. 
            function Person(name) {
                this.name = name
            }
            Person.prototype.sayName = () => {
                console.log('name')
            }
    ​
            // 2. 
            function Stu(age) {
                this.age = age
            }
            Stu.prototype.abc = () => {
                console.log(123)
            }
            Stu.prototype = new Person('QF001')
            Stu.prototype.abc2 = () => {
                console.log(456)
            }
            
            const s = new Stu(18)
            
             console.log(s)
            console.log(s.age)
            console.log(s.name) // QF001
    

借用构造函数继承

  • 核心:借用 call 方法修改 父类构造函数内部的 this 指向
  • 优点:将属性继承在自己身上保留了自己的原型
  • 缺点:只能继承父类的属性,不能继承父类原型上的方法
   function Person(name) {
            this.abc = name
            this.name = name
        }
        Person.prototype.sayName = () => {
            console.log('name')
        }
        function Stu(age) {
            this.age = age
            Person.call(this, 'QF002')
             const s = new Stu(18)
              // 需求 使用 Person 内部的 name 和 sayName 方法
        // console.log(s)
        // console.log(s.age)
        // console.log(s.abc)
        // console.log(s.name)
        // console.log(s.__proto__)
               s.sayName()
    1. 通过 new 关键字调用 Stu 这个构造函数

    2. new 关键会在这个函数内部创建一个空对象, 并且 会把这个函数内部的 this 指定刚才创建的空对象

    3. 我们现在开始正常执行函数代码

      • 3.1给这个对象 添加一个 age 属性, 并且给他赋值 为 参数 age 参数 age === 18

      • 3.2调用 Perosn 这个 函数, 并通过 call 方法 改变这个函数内部的 this 指向, this 指向了 刚才 new 关键字创建的 对象

      • 3.2.1 Person 函数开始执行

      • 给这个对象 添加一个 abc 的属性, 并赋值为 参数 name 参数 name === 'QF002'

      • 给这个对象 添加一个 name 的属性, 并赋值为 参数 name 参数 name === 'QF002'

      1. 现在函数执行完毕, 然后 new关键字 会将刚才创建的 对象 return
      2. 将这个对象 保存在了 变量 s 中
      3. 通过上述流程, 我们认为 这个对象中应该是 {age:18, abc: 'QF002', name: 'QF002'}

组合继承

  • 利用原型继承继承到父类的原型上的方法
  • 利用借用继承继承到父类的 构造函数内部的 属性
  • 好处:
  • 能继承到属性, 并且是在自己对象内部(自己身上)
  • 能够继承到 父类原型上的方法
  • 缺点: 在原型上 有一套多余的属性
  • function Person(name) {
               this.abc = name
               this.name = name
           }
           Person.prototype.sayName = () => {
               console.log('name')
           }
     
           function Stu(age) {
               this.age = age
     
               // 2. 利用 借用继承 继承到 父类的 构造函数内部的 属性
               Person.call(this, 'QF001')
           }
     
           // 1. 利用 原型继承 继承到 父类的 原型上的方法
           Stu.prototype = new Person()
     
           const s = new Stu(18)
     
           console.log(s)
           console.log(s.name)
             /**
            * 查找对象内部属性的时候, 会先在对象内部查找, 找到直接使用, 并停止查找
           */
           s.sayName()
    

拷贝继承

  • 核心: 通过 for in 遍历父类实例化对象上的所有属性, 拷贝到 子类上
  • 它能够遍历 对象内部的 属性, 以及原型上
  • function Person(name) {
               this.name = name
           }
           Person.prototype.sayName = () => {
               console.log('name')
           }
     
           function Stu(age) {
               this.age = age
               const p = new Person('QF001')
     
               for (let key in p) {
                   // console.log(key, p[key])
                   Stu.prototype[key] = p[key]
               }
           }
           Stu.prototype.abc = () => {
               console.log(123)
           }
     
           const s = new Stu(18)
     
           // 需求 使用 Person 内部的 name 何 sayName 方法
           console.log(s)
           console.log(s.age)
           console.log(s.name)
           s.abc()
           s.sayName()
    

ES6 的继承

  • 在书写 子类的时候 语法: class 子类类名 extends 父类类名
  • 在书写 子类 constructor 需要在内部书写 super()
  • 注意:
  • 两个写法必须同时存在,才能完成继承
  • super( )必须在constructor在开始位置
  • function Person (name) {
                this.name = name
            }
            Person.prototype.sayName = function () {
                console.log('name')
            }
    ​
            class Stu extends Person {
                constructor(age) {
                    super('QF001')
                    this.age = age
                }
            }
    ​
            const s = new Stu(18)
            console.log(s)
            console.log(s.age)
            console.log(s.name)
            s.sayName()
    

深浅拷贝

  • 一定是引用数据类型 对象,数组,函数(基本只有对象和数组)
  • 赋值:只要是引用数据类型,那么在赋值的时候,就是引用地址的传递

浅拷贝

  • 遍历对象拿到对象的每一个key与value
  • 然后赋值给另外一个对象,如果所有的value 都是基本数据类型, 那么浅拷贝完成之后, 修改新对象不会影响到老对象
  • 如果 value 有引用数据类型(出现了多层数据结构), 那么浅拷贝只能拷走第一层数据结构, 多层的没有办法处理
 // 2. 浅拷贝
        let o1 = {
            a: 1,
            b: 2,
            c: 3,
            d: {
                d1: '001',
                d2: '002',
                d3: '003'
            }
        }
        let o2 = {}
        for (let key in o1) {
            /**
             *  遍历 o1 对象所有的 key, 然后赋值给 对象 o2
             */
            console.log(key, o1[key])
            o2[key] = o1[key]
        }
        console.log(o1) // {a: 1, b: 2, c: 3}
        console.log(o2) // {a: 1, b: 2, c: 3}
        console.log(o1 === o2)  // false
        o2.b = 999
        console.log(o1) // {a: 1, b: 2, c: 3}
        console.log(o2) // {a: 1, b: 999, c: 3}
​
        console.log(o1)
        console.log(o2)
​
        o2.d.d1 = 'QF666'
        console.log(o1)
        console.log(o2)
​
​

通过JS提供的方法 完成浅拷贝

/**
         *  Object.assign(新对象, 原始对象)
         *      将 原始对象 中所有的 key 全部浅拷贝到 新对象中, 然后返回一个 对象
        */
        // let o3 = Object.assign(o2, o1)
        
          // 2. 浅拷贝
        let o1 = {
            a: 1,
            b: 2,
            c: 3,
            d: {
                d1: '001',
                d2: '002',
                d3: '003'
            }
        }
        let o2 = Object.assign({}, o1)
        console.log(o1)
        console.log(o2)

深拷贝

  • 不管数据有多少层数据结构, 百分百复制出来一份 一摸一样但是毫不相关的数据
  • let o1 = {
              a: 1,
              b: 2,
              c: 3,
              d: {
                  d1: '001',
                  d2: '002',
                  d3: '003'
              }
          }
          let o2 = {}
          function deepClone(target, origin) {
              // 将 origin 完全百分百的复制出来一份, 到 target 中
              for (let k in origin) {
                  // console.log(k, origin[k])
                  if (origin[k].constructor === Object) {
                      target[k] = {}
                      deepClone(target[k], origin[k])
                  } else if (origin[k].constructor === Array) {
                      target[k] = []
                      deepClone(target[k], origin[k])
                  } else {
                      target[k] = origin[k]
                  }
              }
          }
    ​
          deepClone(o2, o1)
          // console.log(o1)
          // console.log(o2)
          o2.d.d2 = 'QF666'
          console.log(o2)
          console.log(o1)
    

JS中提供的方法

  let o1 = {
            a: 1,
            b: 2,
            c: 3,
            d: {
                d1: '001',
                d2: '002',
                d3: '003'
            }
        }
         // JS 中提供的方法
        let o2 = JSON.parse(JSON.stringify(o1))
​
        o2.d.d3 = '9999999'
        console.log(o1)
        console.log(o2)

函数的定义与创建

  • 函数的定义:
  • 在 堆内存中 开辟一段内存空间(XF001)
  • 把函数体的内容 完全 百分百的 照抄一份 存放在 内存空间中(XF001)
  • 把 内存空间的地址(XF001) 赋值给函数名
  • 函数的调用:
  • 根据函数名内存储的 地址 (XF001) 去堆内存中找到对应函数
  • 会去 运行内存中, 开辟一段新的内存, 用于运行函数 (函数作用域)
  • 形参赋值---预解析---函数代码全部执行结束
  • 函数执行完毕之后, 这段内存空间会被销毁
  • function fn() {
              let a = 100
              console.log(a)
          }
          fn()
    

不会销毁的函数执行空间

  • 函数向外部返回了一个引用数据类型(返回的是引用数据类型的地址)
  • 外部有一个变量接受这个 返回值 (外部变量中有 变量 拿到 这个 引用地址)
  • 为了我们后续能够正常使用, JS 不会把这个函数的运行空间 销毁
  • function fn () {
               let obj = {
                   a: 1,
                   b: 2
               }
               return obj
           }
           let o1 = fn()
           // console.log(o1)
     
           o1 = 1000
    

认识闭包

  • 构成条件
  • 需要一个不会被销毁的函数执行空间
  • 需要 直接 或 间接 的返回一个函数
  • 内部函数使用着 外部函数的私有变量
  • 闭包的好处: 延长变量的作用域(使用时间)
  • 函数外部可以使用函数内部的变量了
  • 闭包的弊端: 因为闭包的存在就一定代表一个函数执行空间不会被销毁, 如果大量使用闭包, 会导致内存空间占据严重
  • function outer() {
               let a = 100
               let str = 'QF001'
               let obj = {
                   a: 1,
                   b: 2
               }
               function inner() {
                   return a
               }
               return inner
           }
      
           // 1. 调用 outer 的到了 内层函数 inner, 存到了 变量 res 中
           let res = outer()
      
           /**
            *  2. 调用 res 也就是 调用 内层函数 inner, 调用完成之后 因为函数内部 return str,
            *      所以我的到了一个 字符串
            *      然后 我将这个 字符串 存储到了 newStr 这个变量中
           */
           let newStr = res()
      
           // 3. 打印 newStr 得了 outer 函数中 str 变量
           console.log(newStr) // 100
    

沙箱模式

  • 利用间接返回一个函数,然后去拿到外部函数内的私有变量
  • function outer() {
               let a = 100
               let str = 'QF001'
      
               const obj = {
                   getA: function () {
                       return a
                   },
                   getStr: function () {
                       return str
                   },
                   setA(val) {
                       a = val
                   }
               }
      
               return obj
           }
      
           // 1. 调用了 外部函数 outer , 这个函数内部会返回一个 对象 obj, 然后我将她存储到了 变量 res
           let res = outer()
           console.log(res)
      
           let num = res.getA()
           // console.log(num)    // 100
      
           let newStr = res.getStr()
           // console.log(newStr) // 'QF001'
      
           res.setA(999)
           // console.log(res.getA()) // 999
    
      // 2. 调用了 外部函数 outer, 这个函数内部会返回一个对象 obj, 然后我将它存储到了 变量 res2 中
      let res2 = outer()
      console.log(res2.getA())    // 100
​
​
​
- 案例
​
- ```javascript
   // let count = 1
          function outer() {
              let a = 1
  
              const obj = {
                  getA: function () {
                      return a
                  },
                  setA(val) {
                      a = val
                  }
              }
  
              return obj
          }
          let res1 = outer()   //  内部存储一个对象, 也就是 outer 函数中的 obj 对象
          
          
          
          const sub1 = document.querySelector('.sub1')
          const inp1 = document.querySelector('.inp1')
          const add1 = document.querySelector('.add1')
          sub1.onclick = function () {
              // count--
              // inp1.value = count
  
              let count = res1.getA() // 1
              res1.setA(count - 1)
              inp1.value = res1.getA()
          }
          add1.onclick = function () {
              // count++
              // inp1.value = count
  
              let count = res1.getA() // 1
              res1.setA(count + 1)
              inp1.value = res1.getA()
          }
  
          // let count2 = 1
          let res2 = outer()   //  内部存储一个对象, 也就是 outer 函数中的 obj 对象
          const sub2 = document.querySelector('.sub2')
          const inp2 = document.querySelector('.inp2')
          const add2 = document.querySelector('.add2')
          sub2.onclick = function () {
              // count2--
              // inp2.value = count2
  
              let count = res2.getA() // 1
              res2.setA(count - 1)
              inp2.value = res2.getA()
          }
          add2.onclick = function () {
              // count2++
              // inp2.value = count2
  
              let count = res2.getA() // 1
              res2.setA(count + 1)
              inp2.value = res2.getA()
          }

沙箱模式的语法糖

  • 通过 getter 和 setter 帮助我们简化代码书写

  • function outer() {
                let a = 1
                let b = 100
    ​
                const obj = {
                    get a () {
                        return a
                    },
                    set a (val) {
                        a = val
                    },
                    get b () {
                        return b
                    },
                    set b (val) {
                        b = val
                    }
                }
    ​
                return obj
            }
            let res = outer()
            // console.log(res.getA())  // 这样使用 有问题
            console.log(res)
            console.log(res.a)
            res.a = 999
    ​
            console.log(res.a)
    ​
    ​
            console.log(res.b)