JavaScript之闭包的理解

目录

1.引入:

2.闭包是什么呢?

3.闭包产生的条件:

4.常见的闭包

5.闭包的作用

6.闭包的生命周期

7.闭包的应用

8.闭包的缺点及解决

补充:内存溢出与内存泄漏


1.引入:

我们现在有个需求:有三个按钮,当点击某个按钮时,提示“点击的是第n个按钮”。

代码实现:

    var btns = document.querySelectorAll('button')
    //遍历加监听
    for (var i = 0; i < btns.length; i++) {
      btns[i].onclick = function () {
        console.log(`第${i + 1}个按钮`)
      }
    }

这样写的方式,不管点击哪一个按钮,都会出现“第4个按钮”。

这是因为点击按钮事件只是触发的时候才会调用。当执行到console.log(`第${i + 1}个按钮`)这句代码,点击事件执行时,for循环已经执行结束。整个过程最终只产生了一个i,此时i的值已经变成了3,不管点击那个按钮都是i=3,所以输出3+1=4,就是“第4个按钮”。也就是说,前3次循环时,点击事件并没有执行。

我们可以对代码进行改造:

我们对i进行记录,把每个btn的i相对应起来,知道每个btn按钮的i索引值。这样点击按钮,就会输出相对应的数值。

    var btns = document.querySelectorAll('button')
    for (var i = 0; i < btns.length; i++) {
       //将btn所对应的索引值保存到btn上
      btns[i].index = i
      btns[i].onclick = function () {
        console.log(`第${this.index + 1}个按钮`)
      }
    }

此时可以正常输出。

当然,还有一种方式,将输出语句放在一个匿名的自调用函数里面。

    var btns = document.querySelectorAll('button')
    for (var i = 0; i < btns.length; i++) {
      (function (i) {
        btns[i].onclick = function () {
          console.log(`第${i + 1}个按钮`)
        }
      })(i)
    }

此时也可以正常输出。

 这个就用到了闭包。

2.闭包是什么呢?

当一个嵌套的内部(子)函数引用了嵌套的外部(父)函数的变量(函数)时,就产生了闭包。

  function fn1() {
      var a = 2
      var b = 'abc'
      function fn2() {
        console.log(a)
      }
      fn2()
  }
  fn1()

就是内部嵌套的fn2函数引用了外部fn1函数里的变量a,此时fn2就产生了闭包。

那么闭包到底是什么?

①理解一:闭包是嵌套的内部函数,在这里就是fn2这个函数。

②理解二:包含被引用变量(函数)的对象,也就是在fn2这个内部函数里所引用的变量a。

注意:闭包存在于嵌套的内部函数中。

3.闭包产生的条件:

①函数嵌套

②内部函数引用了外部函数的数据(变量/函数)

③执行调用外部函数

4.常见的闭包

①将函数作为另一个函数的返回值

    function fn1() {
      var a = 2
      function fn2() {
        a++
        console.log(a)
      }
      return fn2
    }
    var f = fn1()
    f()//3
    f()//4

内部函数fn2引用了外部函数fn1的变量a,产生了闭包。

给var a = 2和a++打上断点整个程序运行过程如下:

1.fn1和fn2作为函数提升,fn2中的a++此时为2。

2.语句到return fn2。

3.执行第一个 f()。

4.进入到fn2中执行a++,此时为3,闭包仍存在。

5.语句执行之后再到第二个f()。

6.接下来直接进入到fn2()中执行a++,此时为4。

7.结束。整个过程产生一个闭包,因为fn1只调用了一次。

如果没有闭包,fn1函数一执行,变量a就消失了。后面再调用f()时,就会报错。

②将函数作为实参传递给另一个函数调用

    function showDelay(msg, time) {
      setTimeout(function () {
        alert(msg)
      }, time)
    }
    showDelay('message', 2000)

 内部函数引用了外部函数的message,产生了闭包。

5.闭包的作用

①使用函数内部的变量,也就是局部变量在函数执行完后,仍然存活在内存中(延长了局部变量的生命周期);

②让函数外部可以操作(读写)到函数内部的数据(变量/函数)。

6.闭包的生命周期

①产生:在嵌套内部函数定义执行完时就产生了(函数对象创建时,不是在调用)

②销毁:在嵌套的内部函数成为垃圾对象时

    function fn1() {
      var a = 2//此时闭包就已经产生了(函数提升,内部函数对象已经创建了)
      function fn2() {
        a++
        console.log(a)
      }
      return fn2
    }
    var f = fn1()
    f()//3
    f()//4
    f=null//闭包销毁(包含闭包的函数对象成为了垃圾对象)

7.闭包的应用

定义JS模块:

*具有特定功能的js文件

*将所有的数据和功能都封装在一个函数内部(私有的)

*只向外暴露一个包含n个方法的对象或函数

*模块的使用者,只需要通过模块暴露的对象调用方法来实现对应的功能

code.html文件:

  <script src="./code.js"></script>
  <script>
    var module = myModule()
    module.doSomething()//doSomething()MY CODE
    module.doOtherthing()//doOtherthing()my code
  </script>

code.js文件:

function myModule() {
  // 私有数据
  var msg = 'My code'
  // 操作数据的函数
  function doSomething() {
    console.log('doSomething()' + msg.toUpperCase())
  }
  function doOtherthing() {
    console.log('doOtherthing()' + msg.toLowerCase())
  }
  // 向外暴露对象(给外部使用的方法)
  return {
    doSomething: doSomething,
    doOtherthing: doOtherthing
  }
}

另一种方式实现:

code.html文件:

  <script src="./code2.js"></script>
  <script>
    myModule.doSomething()//doSomething()MY CODE
    myModule.doOtherthing()//doOtherthing()my code
  </script>

code.js文件:

(function myModule() {
  // 私有数据
  var msg = 'My code'
  // 操作数据的函数
  function doSomething() {
    console.log('doSomething()' + msg.toUpperCase())
  }
  function doOtherthing() {
    console.log('doOtherthing()' + msg.toLowerCase())
  }
  // 向外暴露对象(给外部使用的方法)
  window.myModule = {
    doSomething: doSomething,
    doOtherthing: doOtherthing
  }
})()

8.闭包的缺点及解决

缺点:

①函数执行完后,函数内的局部变量没有释放,占用内存时间会变长

②容易造成内存泄露

解决:

①能不用闭包就不用

②及时释放

function fn1(){
  var arr=new Array[1000000]
  function fn2(){
    console.log(arr.length)
  }
  return fn2
}
var f=fn1()
f()
f=null//让内部函数成为垃圾对象,进而回收闭包对象arr

补充:内存溢出与内存泄漏

1.内存溢出:一种程序运行出现的错误。当程序运行需要的内存超过了剩余的内存时,就会抛出内存溢出的错误。

代码示例:(不要轻易尝试,会死机,哈哈哈)

    var obj = {}
    for (var i = 0; i < 10000; i++) {
      obj[i] = new Array(10000000)
      console.log('----')
    }

 Out of Memory(内存不足)

2.内存泄漏:占用的内存没有及时释放,可用内存变小,此时程序还能正常运行。

内存泄漏积累多了就容易导致内存溢出。

常见的内存泄漏:

①意外的全局变量

    function fn() {
      a = 3
      console.log(a)
    }

②没有及时清理的计时器或回调函数

    setInterval(function(){
      console.log('11')
    },1000)

解决:

    var timer=setInterval(function(){
      console.log('11')
    },1000)
    clearInterval(timer)//清除定时器

③闭包

    function fn1() {
      var a = 4
      function fn2() {
        console.log(++a)
      }
      return fn2
    }
    var f = fn1()
  

解决:

    function fn1() {
      var a = 4
      function fn2() {
        console.log(++a)
      }
      return fn2
    }
    var f = fn1()
    f = null

猜你喜欢

转载自blog.csdn.net/weixin_70443954/article/details/128328047