闭包

闭包

闭包概念图

JavaScript 中闭包源于词法作用域与函数当值传递,即函数可以记住并访问所在的词法作用域,即使在当前作用域之外执行仍然保持着对词法作用域的引用。词法作用域是由函数声明时所在的位置决定的,而闭包是词法作用域形成的自然结果。当在函数内部声明了内部函数,并将内部函数作为值返回,就会产生闭包。

  • 词法作用域,就是,按照代码书写时的样子,内部函数可以访问函数外面的变量。引擎通过数据结构和算法表示一个函数,使得在代码解释执行时按照词法作用域的规则,可以访问外围的变量,这些变量就登记在相应的数据结构中。

  • 函数当作值传递,即所谓的 first class 对象。就是可以把函数当作一个值来赋值,当作参数传给别的函数,也可以把函数当作一个值 return。一个函数被当作值返回时,也就相当于返回了一个通道,这个通道可以访问这个函数词法作用域中的变量,即函数所需要的数据结构保存了下来,数据结构中的值在外层函数执行时创建,外层函数执行完毕时理因销毁,但由于内部函数作为值返回出去,这些值得以保存下来。而且无法直接访问,必须通过返回的函数。这也就是私有性。

Lexical Scope(词法作用域)

异步代码中的闭包避免

for (const i = 0; i < 5; i++) {
  setTimeout(function() {
    console.log(new Date(), i);
  }, 1000);
}

console.log(new Date(), i);

IIFE

for (const i = 0; i < 5; i++) {
  (function(j) {
    // j = i
    setTimeout(function() {
      console.log(new Date(), j);
    }, 1000);
  })(i);
}

console.log(new Date(), i);
const output = function(i) {
  setTimeout(function() {
    console.log(new Date(), i);
  }, 1000);
};

for (const i = 0; i < 5; i++) {
  output(i); // 这里传过去的 i 值被复制了
}

console.log(new Date(), i);

Promise

const tasks = []; // 这里存放异步操作的 Promise
const output = i =>
  new Promise(resolve => {
    setTimeout(() => {
      console.log(new Date(), i);
      resolve();
    }, 1000 * i);
  });

// 生成全部的异步操作
for (const i = 0; i < 5; i++) {
  tasks.push(output(i));
}

// 异步操作完成之后,输出最后的 i
Promise.all(tasks).then(() => {
  setTimeout(() => {
    console.log(new Date(), i);
  }, 1000);
});

Async

// 模拟其他语言中的 sleep,实际上可以是任何异步操作
const sleep = timeountMS =>
  new Promise(resolve => {
    setTimeout(resolve, timeountMS);
  });

(async () => {
  // 声明即执行的 async 函数表达式
  for (const i = 0; i < 5; i++) {
    await sleep(1000);
    console.log(new Date(), i);
  }

  await sleep(1000);
  console.log(new Date(), i);
})();
下一页