闭包 | 青训营笔记

100 阅读1分钟

这是我参与「第四届青训营」笔记创作活动的第 13 天

闭包是什么

一个私有作用域,返回一个函数,作用域外只能通过函数操作私有作用域中的变量。

  • 使用场景:需要私有变量,防止污染全局变量

  • 缺陷:不及时回收闭包内部变量,容易引起内存泄漏

function Foo() { 
  var i = 0 
  return function() { 
    console.log(i++) 
  } 
} 
var f1 = Foo(), 
    f2 = Foo(); 
f1(); // 0 
f1(); // 1 
f2(); // 0

首先,Foo 是一个闭包,最直观的判断就是它返回的是一个函数,这个函数可以访问外层函数内的变量。

在上述例子中,i 是 Foo 函数内的局部变量,其他环境是无法访问的(比如 window 或在其他函数内),相反,Foo 内的函数却可以访问外部变量

另外,函数赋值的时候不会执行,也不会开辟新的内存,f1 拿到的只是地址而已,Foo 函数还没有使用,函数内部的变量也没有占用内存,Foo 只是一个字符串而已(以下是大佬的实验)

不需要闭包时,需要将闭包内的变量设为 null

下面看一个小小的🌰

我们期望打印 0 至 5,结果却是 5 个 5

function A() { 
  var arr = [] 
  // var i = 233 但 i 赋值加在这里的话,结果还是 5 个 5 
  for (var i = 0; i < 5; i++) { 
    arr[i] = function () { 
      console.log(i) 
    } 
  } 
  // var i = 233 如果这里加一行的话,结果会变成 5 个 233 
  return arr 
} 
var myArr = A() 

for (var j = 0; j < 5; j++) { 
  myArr[j]() // 5 个 5 
}

形成这个结果的原因主要有两个:

  1. for 循环执行时所建立的是块级作用域 {} ,块级作用域中使用 var 定义的变量还是属于该作用域所在的函数作用域,也就是 logFun
  2. 函数的作用域是静态作用域,也就是定义的时候就确定了,函数调用的时候会函数定义时形成的作用域链中查找所需参数,for 所在的块级作用域 logFuni == 5 ,所以会打印 5 个 5

解决方法 1:使用立即执行函数

function logFunc() { 
  var arr = [] 
  for (var i = 0; i < 5; i++) { 
    (function(j){ 
      arr[j] = function () { 
        console.log(j) 
      } 
    }(i)) 
  } 
  return arr 
} 

var myArr = logFunc() 
for (var j = 0; j < 10; j++) { 
  myArr[j]() 
}

解决方法 2:将 for 循环里的 var 改为 let

使它成为 for 块级作用域中的私有变量

参考:《JavaScript闭包的底层运行机制》