2

什么是闭包

MDN给出的定义是可以从内部函数访问外部函数的作用域的一种状态
闭包并不是稀奇玩意, 在JavaScript中闭包无处不在, 并且闭包并不是一个语法, 而是基于词法-----根据源代码中声明变量的位置来确定该变量在何处可用-----作用域写代码时所产生的自然而然的结果, 因此在实际书写中也许没有刻意使用闭包, 但大概率会产生闭包, 比如:

function init() {
  let name = "sifou"; // name 是一个被 init 创建的局部变量
  function displayName() { // displayName() 是内部函数,一个闭包
    alert(name); // 使用了父函数中声明的变量
  }
  displayName();
}
init(); // sifou

根据前面的定义, 看起来并不像是, 因为displayName函数时嵌套在init内的, 根据作用域链的查找规则, 可以使用上层作用域中的变量是正常的, 而这条查找规则就是产生闭包的最重要的原因. 修改一下上面代码:

function init() {
  let name = "sifou"; // name 是一个被 init 创建的局部变量
  function displayName() { // displayName() 是内部函数,一个闭包
    alert(name); // 使用了父函数中声明的变量
  }
  return displayName
}
const outFn = init();
outFn() // sifou

由于函数可以作为值进行传递, 因此outFndisplayName实际上是通过不同的标识符调用了内部函数displayName, 而且是在自己定义时的词法作用域外面执行的. 按理来说, 当init执行结束后应该垃圾回收机制回收, 其整个内部作用域都应该被销毁. 而闭包阻止了这件事发生因为该作用域依然被使用, 被displayName使用. displayName依然持有该作用域的引用整个引用就是闭包. 理所当然的, 也就意味着无论以何种方式对函数类型的值进行传递, 当该函数在别处进行调用的时候就可以看到闭包. 本质上一般来说如果将函数作为值类型传递就会有闭包. 比如定时器, 事件监听器, Ajax请求等任务中, 只要使用回调函数实际上就是闭包, 正应了开头所说的JavaScript中闭包无处不在

一个经典的闭包面试题:

// 改造使其打印1,2,3,4,5
for (var i = 1; i <= 5; i++) {
  setTimeout(function timer() {
    console.log(i)
  }, i * 1000)
}

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

改造后可以打印1,2,3,4,5的原因与使用let有相似的原因----块级作用域, 在迭代内部使用IIFE会为每一个迭代都生成一个新的作用域, 使延迟函数的回调可以将新的作用域封闭在每个迭代的内部, 并且由于闭包的存在(timer对形参j的使用), IIFE执行后不会被回收, 所以每次的迭代中都有一个正确的变量值进行访问


玛拉_以琳
8.7k 声望6.2k 粉丝