闭包

53 阅读3分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第16天,点击查看活动详情

前提知识点: 执行上下文和垃圾回收机制

闭包是指一个函数可以访问另一个函数作用域中变量,创建闭包的最常见的方式就是在一个函数内创建另一个函数,创建的函数可以访问到当前函数的局部变量。

闭包是作用域的一种特殊应用. 有两种常用用途

  • 函数作为参数被传递
  • 函数作为返回值被返回
  • 闭包的第一个用途是使我们在函数外部能够访问到函数内部的变量。通过使用闭包,可以通过在外部调用闭包函数,从而在外部访问到函数内部的变量,可以使用这种方法来创建私有变量。
  • 闭包的另一个用途是使已经运行结束的函数上下文中的变量对象继续留在内存中,因为闭包函数保留了这个变量对象的引用,所以这个变量对象不会被回收。

详细资料可以参考: 《JavaScript 深入理解之闭包》

image-20220203201642770

左边结果是 100, 右边结果是 100. 所有的自由变量的查找, 是函数定义的地方向上级作用于查找, 而不是在执行的地方.

比如,函数 A 内部有一个函数 B,函数 B 可以访问到函数 A 中的变量,那么函数 B 就是闭包。

 function A() {
   let a = 1
   window.B = function () {
       console.log(a)
   }
 }
 A()
 B() // 1

在 JS 中,闭包存在的意义就是让我们可以间接访问函数内部的变量。经典面试题:循环中使用闭包解决 var 定义函数的问题

 for (var i = 1; i <= 5; i++) {
   setTimeout(function timer() {
     console.log(i)
   }, i * 1000)
 }

首先因为 setTimeout 是个异步函数,所以会先把循环全部执行完毕,这时候 i 就是 6 了,所以会输出一堆 6。解决办法有三种:

  • 第一种是使用闭包的方式
 for (var i = 1; i <= 5; i++) {
   ;(function(j) {
     setTimeout(function timer() {
       console.log(j)
     }, j * 1000)
   })(i)
 }

在上述代码中,首先使用了立即执行函数将 i 传入函数内部,这个时候值就被固定在了参数 j 上面不会改变,当下次执行 timer 这个闭包的时候,就可以使用外部函数的变量 j,从而达到目的。

  • 第二种就是使用 setTimeout 的第三个参数,这个参数会被当成 timer 函数的参数传入。
 for (var i = 1; i <= 5; i++) {
   setTimeout(
     function timer(j) {
       console.log(j)
     },
     i * 1000,
     i
   )
 }
  • 第三种就是使用 let 定义 i 了来解决问题了,这个也是最为推荐的方式
 for (let i = 1; i <= 5; i++) {
   setTimeout(function timer() {
     console.log(i)
   }, i * 1000)
 }

1.1. 实际应用

1.for 循环定时器闭包

2.防抖节流

隐藏数据, 不被外界访问

3.做一个简单的 cache 工具

 function createCache(){
     const data = {}
     return {
         set: function (key,val){
             data[key]=val
         },
         get: function(key){
             return data[key]
         }
     }
 }
 ​
 const c = createCache()
 c.set('a',100)
 console.log(c.get('a'))