认识继承的方法,沙箱盒子,闭包,深浅拷贝

66 阅读7分钟

认识继承

  • 如果想让某个对象内部有其他构造函数的属性, 并且可以使用构造函数其中的原型空间,就可以通过继承的方式,让这个类, 继承上另外一个类内部的属性与原型上的方法

原型继承

  • 核心: 就子类的原型指向父类的实例化对象
  • 优点: 实现了继承
  • 缺点:
    • 继承了属性, 但是不在自己身上

    • 失去了自己的原型

        function Person(name) {
            this.name = name
        }
        Person.prototype.sayName = () => {
            console.log('name')
        }
      
        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.age)  // 18
        console.log(s.name) // QF001
      
      1. 去 对象内部查找 name 属性, 然后发现对象内部没有这个属性
      1. 去这个对象内部的 proto 上查找, 对象的__proto__ 指向了自己构造函数的 原型 也就是 指向了 Stu.prototype
      1. 因为我们手动修改了 Stu.prototype, 给他赋值为了 Person 构造函数的实例化对象
      1. 也就是说 我们 Stu.prototype 就指向了 Person的实例化对象
      1. Person 的实例化对象 内部 有一个属性叫做 name, 此时找到并返回

借用构造函数继承

  • 核心: 借用 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, 'abc')
       }
       const s = new Stu(18)
       s.sayName()
      
      1. 通过 new 关键字调用 Stu 这个构造函数
      1. new 关键会在这个函数内部创建一个空对象, 并且 会把这个函数内部的 this 指定刚才创建的空对象
      1. 我们现在开始正常执行函数代码
      • 3.1 给这个对象 添加一个 age 属性, 并且给他赋值 为 参数 age 参数 age === 18
      • 3.2 调用 Perosn 这个 函数, 并通过 call 方法 改变这个函数内部的 this 指向, this 指向了 刚才 new 关键字创建的 对象
        • 3.2.1 Person 函数开始执行
          • 给这个对象 添加一个 abc 的属性, 并赋值为 参数 name 参数 name === 'abc'
          • 给这个对象 添加一个 name 的属性, 并赋值为 参数 name 参数 name === 'abc'
      1. 现在函数执行完毕, 然后 new关键字 会将刚才创建的 对象 return
      1. 将这个对象 保存在了 变量 s 中
      1. 通过上述流程, 我们认为 这个对象中应该是 {age:18, abc: 'abc', name: 'abc'}

组合继承

    1. 利用 原型继承 继承到 父类的 原型上的方法
    1. 利用 借用继承 继承到 父类的 构造函数内部的 属性
    • 好处:

        1. 能继承到属性, 并且是在自己对象内部(自己身上)
        1. 能够继承到 父类原型上的方法
    • 缺点: 在原型上 有一套多余的属性

        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, 'abc')
        }
      
        // 1. 利用 原型继承 继承到 父类的 原型上的方法
        Stu.prototype = new Person()                                                                     
      
        const s = new Stu(18)
      
        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('abc')
      
            for (let key in p) {
                Stu.prototype[key] = p[key]
            }
        }
        Stu.prototype.abc = () => {
            console.log(123)
        }
      
        const s = new Stu(18)
      
        // 使用 Person 内部的 name 何 sayName 方法
        console.log(s.age)
        console.log(s.name)
        s.abc()
        s.sayName()
      

ES6 的继承

    1. 在书写 子类的时候 语法: class 子类类名 extends 父类类名
    1. 在书写 子类 constructor 需要在内部书写 super()
    • 注意:
        1. 两个写法 必须同时存在, 才能完成继承
        1. super() 必须在 constructor 在开始位置,
        1. 类的继承, 除了能够继承 calss 类, 还能够继承 ES5 的构造函数

             function Person (name) {
                 this.name = name
             }
             Person.prototype.sayName = function () {
                 console.log('name')
             }
          
             class Stu extends Person {
                 constructor(age) {
                     super('abc')
                     this.age = age
                 }
             }
          
             const s = new Stu(18)
             console.log(s.age)
             console.log(s.name)
             s.sayName()
          

深浅拷贝

  • 一定是引用数据类型,对象, 数组, 函数(基本只有对象和数组)

  • 赋值

    • 只要是引用数据类型, 那么在赋值的时候, 就是引用地址的传递

浅拷贝

  • 遍历对象拿到对象的每一个key与value

  • 然后赋值给另外一个 对象

  • 如果所有的 value 都是基本数据类型, 那么浅拷贝完成之后, 修改新对象不会影响到老对象

  • 如果 value 有引用数据类型(出现了多层数据结构), 那么浅拷贝只能拷走第一层数据结构, 多层的没有办法处理

           // 1. 赋值
          const s1 = '123'
          let s2 = s1   // 赋值
          console.log(s2 === s1)
          s2 = '456'
          console.log(s2) // 456
          console.log(s1) // 123
    
          // 浅拷贝
          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)
    

深拷贝

  • 不管数据有多少层数据结构, 百分百复制出来一份 一摸一样但是毫不相关的数据

函数的定义与创建

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

不会销毁的函数执行空间

    1. 函数向外部返回了一个引用数据类型(返回的是引用数据类型的地址)
    1. 外部有一个变量接受这个 返回值 (外部变量中有 变量 拿到 这个 引用地址)
    1. 为了我们后续能够正常使用, JS 不会把这个函数的运行空间 销毁

         function fn () {
             let obj = {
                 a: 1,
                 b: 2
             }
             return obj
         }
         let o1 = fn()
      

认识闭包

  • 构成条件:

      1. 需要一个不会被销毁的函数执行空间
      1. 需要 直接 或 间接 的返回一个函数
      1. 内部函数使用着 外部函数的私有变量
  • 闭包的好处: 延长变量的作用域(使用时间)

    • 函数外部可以使用函数内部的变量了
  • 闭包的弊端: 因为闭包的存在就一定代表一个函数执行空间不会被销毁, 如果大量使用闭包, 会导致内存空间占据严重

          function outer() {
              let a = 100
              let str = 'abc'
              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 = 'abc'
          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) // 'abc'
    
      res.setA(999)
      // console.log(res.getA()) // 999
    
      // 2. 调用了外部函数 outer, 这个函数内部会返回一个对象 obj, 然后我将它存储到了变量res2 中
      let res2 = outer()
      console.log(res2.getA())    // 100
    

沙箱模式的语法糖

  • 语法糖: 再不影响功能的情况下, 对我们的语法做一点简化操作

  • 通过 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.a)
      res.a = 999
      console.log(res.a)