JS 闭包

39 阅读2分钟

闭包是一种特殊的对象。

它由两部分组成。执行上下文(代号A),以及在该执行上下文中创建的函数(代号B)。

当B执行时,如果访问了A中变量对象中的值,那么闭包就会产生。

在大多数理解中,包括许多著名的书籍,文章里都以函数B的名字代指这里生成的闭包。而在chrome中,则以执行上下文A的函数名代指闭包。

因此一个闭包对象,由A、B共同组成,下面以chrome的标准来称呼。

var fn = null;
function foo(){
  var a = 2;
  function innerFoo(){
    console.log(a)
    debugger
  }
  fn = innerFoo; // 将innerFoo 的引用,赋值给全局变量中的 fn
}

function bar () {
  fn(); // 此处的保留的 innerFoo 的引用
}

foo();
bar(); // 2

image.png

  • Closure :为闭包,
  • Call Stack :为当前的函数调用栈,
  • Scope :为当前正在被执行的函数的作用域链,
  • Local :为当前的局部变量。

所以,通过闭包,可以在其他的执行上下文中,访问到函数的内部变量。

在闭包中,能访问到的变量,仍然是作用域链上能够查询到的变量。

对上面的例子稍作修改,如果在函数 bar 中声明一个变量 c,并在闭包 fn 中试图访问该变量,运行结果会抛出错误。

setTimeout(function() {
  console.log('一秒后打印')
}, 1000)

image.png

每一个 setTimeout 在执行时,会返回一个唯一 ID,上图中的数字 5,就是这个唯一 ID。我们在使用时,常常会使用一个变量将这个唯一 ID 保存起来,用以传入 clearTimeout,清除定时器。

页面中所有由 setTimeout 定义的操作,都将放在同一个队列中依次执行。

队列:先进先出

而这个队列执行的时间,需要等待到函数调用栈清空之后才开始执行。即所有可执行代码执行完毕之后,才会开始执行由 setTimeout 定义的操作。而这些操作进入队列的顺序,则由设定的延迟时间来决定。

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