谈谈对闭包的理解

82 阅读2分钟

《用得上的前端知识》系列 - 你我都很忙,能用100字说清楚,绝不写万字长文

概念

闭包的定义

  • 技术/理论角度的定义:闭包是函数和声明该函数的词法环境的组合;
    • 所有的JavaScript函数都是闭包。 因为函数声明时会设置一个内置属性[[Environment]]来记录当前执行上下文的词法环境。
  • 实践角度的定义:闭包就是能够读取其他函数内部变量的函数;
    • 最简单的闭包就是父函数内返回一个函数,返回函数内引用了父函数内变量;
  • 在 chrome 中,闭包的名称是外层函数的函数名。

自由变量:在函数中使用,但却不是函数形参或局部变量的变量。

内存泄漏:是指在计算机科学中,由于疏忽或错误造成程序未能释放已经不再使用的内存的现象。

用途

  • 创建私有变量;
  • 延长变量的生命周期(让特定变量的值始终保存在内存中)。

注意事项

  • 过多的使用 闭包 会导致内存消耗大,在某些浏览器(如 IE)中会产生内存泄漏。
    • 解决办法:在函数退出前,清空或删除不再使用的变量。
  • 闭包允许内层函数引用父函数中的变量,但是该变量是最终值。
  • 如果闭包会一直使用,则可以作为全局变量存在;若使用频率不高,尽量让它作为一个局部变量。

闭包经典面试题

// 题目:修改以下代码,使之每隔一秒的时间输出 0 -> 1 -> 2 -> 3 -> 4 -> 5
for (var i = 0; i < 5; i++) {
  setTimeout(function() {
    console.log(new Date, j);
  }, 1000);
}

console.log(new Date, i);

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

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

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


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

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

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

参考资料