javascript闭包那点事

65 阅读2分钟

定义

MDN对闭包的定义为:

闭包(closure)是一个函数以及其捆绑的周边环境状态(lexical environment,词法环境)的引用的组合。换而言之,闭包让开发者可以从内部函数访问外部函数的作用域。在 JavaScript 中,闭包会随着函数的创建而被同时创建。

分析

让我们先写个例子

var scope = 'global scope'
function checkscope() {
  var scope = 'local scope'
  function f() {
    return scope
  }
  return f
}

var foo = checkscope()
foo()

首先我们要分析一下这段代码中执行上下文栈和执行上下文的变化情况。

这里直接给出简要的执行过程:

  1. 进入全局代码,创建全局执行上下文,全局执行上下文压入执行上下文栈
  2. 全局执行上下文初始化
  3. 执行 checkscope 函数,创建 checkscope 函数执行上下文,checkscope 执行上下文被压入执行上下文栈
  4. checkscope 执行上下文初始化,创建变量对象、作用域链、this 等
  5. checkscope 函数执行完毕,checkscope 执行上下文从执行上下文栈中弹出
  6. 执行 f 函数,创建 f 函数执行上下文,f 执行上下文被压入执行上下文栈
  7. f 执行上下文初始化,创建变量对象、作用域链、this 等
  8. f 函数执行完毕,f 函数上下文从执行上下文栈中弹出

了解到这个过程,我们应该思考一个问题,那就是:

当 f 函数执行的时候,checkscope 函数上下文已经被销毁了啊(即从执行上下文栈中被弹出),怎么还会读取到 checkscope 作用域下的 scope 值呢?

是的, JavaScript 是可以的!

经典题

// 原始题目
for (var i = 0; i < 5; i++) {
  setTimeout(function () {
    console.log(i); // 1s后打印出5个5
  }, 1000);
}

// 利用闭包,将上述题目改成1s后,打印0,1,2,3,4

// 方法一:
for (var i = 0; i < 5; i++) {
  (function (j) {
    setTimeout(function timer() {
      console.log(j);
    }, 1000);
  })(i);
}

// 方法二:
// 利用setTimeout的第三个参数,第三个参数将作为setTimeout第一个函数的参数
for (var i = 0; i < 5; i++) {
  setTimeout(
    function fn(i) {
      console.log(i);
    },
    1000,
    i
  ); // 第三个参数i,将作为fn的参数
}

// 将上述题目改成每间隔1s后,依次打印0,1,2,3,4
for (var i = 0; i < 5; i++) {
  setTimeout(
    function fn(i) {
      console.log(i);
    },
    1000 * i,
    i
  );
}

欢迎关注微信公众号【前端小组】,最新的文章会先发公众号,前端进阶同行。