【闭包】closure(看完不会你砍我!!!)

前言:

  闭包是js中最强大的特性,也是js相较于其他语言最令人着迷的地方,如果你对它研究的透彻,你会为它着迷,否则你会被吓住。

  请仔细阅读文中的判断句,如果对某句话不理解可以留言,我会回复的,或者一起讨论怎么描述更为准确。

 

闭包的前置知识点:

  1、在函数中如果不使用var定义变量,那么js引擎会自动将该变量添加为全局变量。这个叫做js的变量的声明前置

  2、全局变量在声明的那刻起就一直在内存中,除非关闭这个页面

      let number = 0
      function a() {
        console.log(++number)
      }
      a() // 1
      a() // 2

  3、局部变量在函数运行完后销毁,下一次调用该函数再重新创建该局部变量

      function a() {
        let number = 0
        console.log(++number)
      }
      a() // 1
      a() // 1

  4、函数内部可以使用局部变量,也可以使用全局变量,也可以使用它的父级函数的局部变量。函数外不可以使用某个函数的局部变量(第3点,a函数外是不能访问到number变量的)

  5、垃圾回收机制:每隔一段时间,垃圾回收器去内存中找到那些不再使用的值,然后给它释放掉,一次来缓解内存的压力。如果一个函数被【全局】变量引用(将函数赋值给该变量)了,那么它将不会被垃圾回收机制回收,这种情况多了就会造成内存拥堵,严重时会造成【内存泄漏】

  6、词法环境(词法作用域):根据变量声明的位置确定该变量在何处可用(重点)----也可以说,当一个函数执行时和声明时的词法作用域不是同一个,闭包就产生了

      function test(fn) {
        const a = 1
        fn()
      }
      const a = 2
      function fn() {
        console.log(a)
      }
      test(fn) // 2   为什么是2不是1?取决于函数声明时用的是哪个a

    多提一句:this的值是在函数执行时决定,而不是函数定义时决定,它俩正好相反。this就是谁调用了我,我就指向谁

 

闭包解决了什么问题:

  js有回收机制,如果一个函数没有被引用,该函数执行完后它的作用域就会被销毁;如果该函数被引用了,它执行完后作用域将不会被销毁。

 

闭包的定义:

  MDN:一个函数和对其周围状态(lexical environment,词法环境)的引用捆绑在一起(或者说函数被引用包围),这样的组合就是闭包closure)。

  

    【在JavaScript中,每当创建一个函数,闭包就会在函数创建的同时被创建出来】,也就是说,所有的函数,都是闭包。整个浏览器都是一个作用域,其中的每个函数(如fn)都是作为window的一个属性,window对象为父函数,fn就是子函数,调用fn时通常前面的【window.】不写,这就是一个函数嵌套函数的关系。所有的函数都有父级,所以都是子函数,所以函数都是闭包。

 

  JavaScript高级程序设计第三版:闭包是指有权访问另一个函数作用域中的变量的函数。(子函数就是一个闭包)

    常见的方式:函数内部创建一个函数

  JavaScript高级程序设计第四版:闭包指的是那些引用了另一个函数作用域中变量的函数,通常是在嵌套函数中实现的。(子函数中引用了父函数的变量,那么子函数就是一个闭包)

 

形成闭包的条件:

  1、函数嵌套函数

  2、内部函数引用了外部函数的局部变量/局部函数【也就是说,如果b函数中没有使用a函数中的变量,那么在b执行的过程中不会产生闭包】

    注意:旧版谷歌浏览器中即使不调用内部函数也会产生闭包,新版谷歌不调用内部函数时不会产生闭包。

  

  

  b函数在执行时产生了闭包,b函数就是一个闭包,但是这个闭包没有被保持下来,在b执行完后闭包就已经没了。

  如果说闭包没有被保持下来,那么闭包的作用就得不到体现。

 

  例子,判断b函数中有没有形成闭包:

      function a() {
        var num = 100
        var s = 'xxx'
        function b() {
          console.log(b) // 使用了a函数中的局部函数,此时会形成闭包 { b: fun }
        }
        b()
      }
      a()
      /*
        预编译过程:
          当a调用时,产生a的AO对象
            aAO: { 
              num: 100,
              str: 'xxx',
              b: fun
            }
          当b调用时,产生b的AO对象
            bAO: {}    b里面没有可预编译的东西
            代码执行到b函数中,此时的[[Scopes]]为:
              0: bAO
              1: aAO
              2: GO
            打印的b在b函数中没有,向上查找,在a中找到局部函数b,即打印b的function
      */

 

闭包的保持:

  如果希望在函数调用后,闭包依然保持,就需要将内部函数返回到外部函数的外部

      /*
        闭包的保持:将内部函数返回到外部函数的外面
      */
      function a() {
        var num = 100
        return function() {
          console.log(num++) // 使用了外部函数的num,这里形成闭包
        }
      }
      var b = a()
      console.dir(b)
      b() // 结果:100    执行b产生bAO    b函数中使用了a函数中的num,这个num就被保存在内存中,不被垃圾回收机制回收
      b() // 结果:101    执行b产生另一个bAO

 

什么时候需要使用闭包:

  一般来说,函数外是不能访问函数内的变量的,闭包就是来解决这个问题

  想让一个变量长期存储在内存中,以便将来使用,但是不想定义全局变量,以免该变量易受到污染,就要想到使用闭包

 

闭包的应用:

  1、在函数外部访问私有变量

      /*
        闭包的应用:函数外部可以访问函数内部的变量
      */
      function a() {
        var num = 100
        return function() {
          return num++
        }
      }
      var b = a()
      var num = b()
      var num1 = b()
      console.log(num) // 100
      console.log(num1) // 101

  2、实现封装,私有属性和私有方法

  3、防止污染全局变量

  4、回调函数的本质是利用了闭包的特性,将局部变量缓存起来了

    

  ……

  闭包的实际应用

 

闭包的作用:

      ①闭包可以使私有变量不被垃圾回收机制回收,这样,当我们需要使一个变量长期存储在内存中,就可以使用闭包代替全局变量

      ②

闭包的缺点:

      函数中用var定义的变量在该函数运行完即被销毁。而在闭包中,内层函数调用了外层函数的局部变量,并且返回给外面的全局变量,该局部变量会被存储起来。因为外层函数返回的是一个函数(返回了内层函数),函数就是一个对象,所以该局部变量被保存到了堆中,即使将接收的那个全局变量设置为null,也不会将该局部变量销毁,这样就保存了外层函数的私有变量了,同时也可能会造成内存泄漏。

 

案例:求数组的一段区间

      const arr = [1, 23, 5, 6, 34, 26, 78, 9]
      const a1 = arr.filter(function (item) {
        return item >= 2 && item <= 9
      })
      console.log(a1)
      const a2 = arr.filter(function (item) {
        return item >= 3 && item <= 6
      })
      console.log(a2)

  这段代码里filter中的代码重复,可以使用闭包进行优化

      function between(a, b) {
        return function (item) {
          return item >= a && item <= b
        }
      }
      // const between = (a, b) => (item) => item >= a && item <= b
      console.log(arr.filter(between(2, 9)))
      console.log(arr.filter(between(3, 6)))

 

案例:数组对象根据某个属性排序

      const goods = [
        { name: '苹果', price: 10, num: 52 },
        { name: '梨子', price: 4, num: 200 },
        { name: '芒果', price: 12, num: 150 },
        { name: '香蕉', price: 8, num: 32 },
        { name: '火龙果', price: 11, num: 22 },
        { name: '橙子', price: 15, num: 88 }
      ]
      const priceOrder = goods.sort((a, b) => a.price - b.price)
      console.table(priceOrder)

      const numOrder = goods.sort((a, b) => a.num - b.num)
      console.table(numOrder)

  sort函数中那段代码可以利用闭包复用

      const order = (propertyName) => (a, b) => a[propertyName] - b[propertyName]

      console.table(goods.sort(order('price')))
      console.table(goods.sort(order('num')))

 

内存泄漏的解决方法:

    <div desc="aaa">aaa</div>
    <div desc="bbb">bbb</div>
    <script>
      // 要求点击div打印它的自定义属性desc
      const divs = document.querySelectorAll('div')

      // item被保存到内存中,但是并不需要它。内存中这样无用的数据多了到一定量会造成内存泄漏
      // divs.forEach((item) => {
      //   item.addEventListener('click', () => {
      //     console.log(item.getAttribute('desc'))
      //     console.log(item) // <div desc="aaa">aaa</div>
      //   })
      // })

      // 获取到item的desc后将item设置为null,将不必要
      divs.forEach((item) => {
        const desc = item.getAttribute('desc')
        item.addEventListener('click', () => {
          console.log(desc)
          console.log(item) // null
        })
        item = null // 将item设置成null,就会被垃圾回收机制回收
      })
    </script>

  

  也可以使用bind

      divs.forEach((item) => {
        item.addEventListener('click', fn.bind(this, item))
        item = null
      })
      function fn(item) {
        console.log(item.getAttribute('desc'))
      }

 

this在闭包中的历史遗留问题:

      const person = {
        username: '小明',
        getName: function () {
          console.log(this.username) // 小明
          return function () {
            console.log(this) // window
            return this.username // 闭包按理来说可以访问到上级函数中的变量,但是this比较特殊。this的指向在于被谁调用,a函数是被window调用的,所以这里的this是window,所以会打印undefined
          }
        }
      }
      const a = person.getName()
      console.log(a()) // undefined

  解决:

      const person = {
        username: '小明',
        getName: function () {
          const _this = this
          return function () {
            console.log(_this) // {username: '小明', getName: ƒ}
            return _this.username // 小明
          }
        }
      }
      const a = person.getName()
      console.log(a()) // 小明

    或者使用箭头函数

      const person = {
        username: '小明',
        getName: function () {
          return () => {
            console.log(this) // {username: '小明', getName: ƒ}
            return this.username // 小明
          }
        }
      }
      const a = person.getName()
      console.log(a()) // 小明

  【var和let/const的一个区别】:

      const person = {
        username: '小明',
        getName: function() {
          return function() {
            return this.username // 小红
          }
        }
      }
      var username = '小红' // var定义的变量会挂载到window上,let和const不会,这里如果用let或const定义,还是打印undefined
      const a = person.getName()
      console.log(a()) // 小红

 

案例:每隔一秒在页面打印一次当前时间

      let second = 0
      function counter() {
        return ++second
      }
      const recordSecond = setInterval(function () {
        if (second === 5) {
          clearInterval(recordSecond)
          console.log('计时结束')
          return
        }
        const str = counter() + '秒'
        console.log(str)
      }, 1000)

  改成闭包

      let doCounter = counter()
      function counter() {
        let second = 0
        return function () {
          if (second === 5) {
            clearInterval(recordSecond)
            doCounter = null // 清除闭包:将引用内层函数的变量赋值为null
            console.log('计时结束')
            return
          }
          second++
          console.log(second + '秒')
        }
      }
      const recordSecond = setInterval(function () {
        doCounter()
      }, 1000)

 

posted @ 2019-07-18 01:20  吴小明-  阅读(339)  评论(0编辑  收藏  举报