zoukankan      html  css  js  c++  java
  • 【闭包】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)
  • 相关阅读:
    android基本控件学习-----Date&Time
    android基本控件学习-----ScrollView
    android基本控件学习-----SeekBar&RatingBar
    android基本控件学习-----ProgressBar
    android基本控件学习-----ToggleButton&Switch
    android基本控件学习-----RadioButton&CheckBox
    android基本控件学习-----ImageView
    android基本控件学习-----Button
    android基本控件学习-----EditText
    android基本控件学习-----TextView
  • 原文地址:https://www.cnblogs.com/wuqilang/p/11204625.html
Copyright © 2011-2022 走看看