【前端每天一题】🔥 第 1 题:什么是 闭包(Closure)?它有什么作用?

闭包(Closure)面试详解

本文覆盖概念、本质、示例、常见陷阱、答题模板与练习题,便于背记和变形作答。

0) 一句话高分答案

闭包是函数与其定义时的词法环境(Lexical Environment)的组合;即函数可以"记住并访问"定义时的外层作用域变量,即使外层作用域已结束。常用于封装私有变量、延长变量生命周期、实现工厂函数/模块等。

1) 概念详解

  • 词法作用域:函数定义的位置决定了它能访问哪些外层变量(不是调用位置)。
  • 闭包:当一个函数引用了外部函数的变量并被外部返回或使用时,该函数形成闭包。闭包保存的是"对外层环境的引用",因此被引用的变量不会被回收。

2) 为什么会有闭包(实现角度)

  • 引擎为每个执行上下文维护词法环境,包含变量绑定及指向外层环境的引用。
  • 若某变量被仍可访问的内部函数引用,则其绑定必须留在内存中;闭包就是"函数 + 可访问的词法环境"。

3) 常见用途(面试要点)

  1. 模拟私有变量 / 信息隐藏
  2. 工厂函数 / 生成器模式(返回预配置的函数)
  3. 函数记忆(memoization)
  4. 保持某些状态(如计数器)
  5. 部分应用 / 柯里化(保存部分参数)
  6. 在事件/回调中携带上下文状态

4) 经典示例(逐行解释)

js 复制代码
function createCounter() {
  let count = 0;            // 外层变量
  return function () {      // 内部函数引用了 count
    count++;
    return count;
  };
}

const counter = createCounter();
console.log(counter()); // 1
console.log(counter()); // 2

解释:createCounter 执行完后,其执行上下文应被释放。但返回的内部函数仍引用 count,因此 count 继续存在,counter 与其词法环境形成闭包。

5) 面试常见陷阱与解析

题 A:for 循环 + var(最常见)

js 复制代码
var funcs = [];
for (var i = 0; i < 3; i++) {
  funcs.push(function () {
    console.log(i);
  });
}
funcs[0](); // 3
funcs[1](); // 3
funcs[2](); // 3

原因:var i 是函数作用域,三个函数引用的是同一个 i,循环结束后 i === 3

修复:

  • 使用 let i(块级作用域):
js 复制代码
for (let i = 0; i < 3; i++) {
  funcs.push(() => console.log(i));
}
  • 或使用 IIFE 捕获当前值:
js 复制代码
for (var i = 0; i < 3; i++) {
  (function (j) {
    funcs.push(function () { console.log(j); });
  })(i);
}

题 B:闭包与内存占用

闭包会延长被引用变量的生命周期。如果大量闭包保留巨大数据结构且长时间不释放,可能导致内存高占用。需避免无谓保留大对象(例如把大量 DOM 节点放入长期闭包)。

题 C:闭包与 this 的误区

闭包与 this 是两回事:闭包是词法环境,this 是运行时绑定。闭包不会改变 this 的行为,但闭包中的函数仍受 this 规则影响(call/apply/bind)。

6) 追问点与回答思路

  • 为什么闭包不会被回收?因为内部函数持有对外层变量的引用,引用存在即不回收。
  • 闭包的性能问题?延长变量生命周期、增加内存占用;避免在全局长期引用大量数据,必要时主动解除引用(如置 null)。
  • 和模块模式关系?模块常利用闭包隐藏私有状态,只暴露必要 API(IIFE + 返回对象)。

7) 标准答题结构(简洁完整)

  1. 一句定义(函数 + 词法环境)
  2. 为什么产生(词法作用域 + 引擎词法环境)
  3. 常见用途(私有变量、工厂函数等)
  4. 简单举例(计数器)
  5. 陷阱/性能补充(如 for/var 示例)

8) 练习题

练习 1:预测输出

js 复制代码
function makeAdd(x) {
  return function(y) {
    return x + y;
  }
}
const add5 = makeAdd(5);
console.log(add5(2)); // 7

练习 2:修复 for 闭包

js 复制代码
var funcs = [];
for (var i = 0; i < 3; i++) {
  funcs.push(function() { console.log(i); });
}
// 改为:
for (let i = 0; i < 3; i++) {
  funcs.push(() => console.log(i));
}
funcs.forEach(f => f());

练习 3:实现带 reset 的计数器

js 复制代码
function createCounter() {
  let count = 0;
  return {
    inc() { count++; return count; },
    value() { return count; },
    reset() { count = 0; }
  };
}

9) 常见变形题(加分项)

  • 闭包在异步场景(setTimeout/Promise)的工作机制
  • 用闭包实现 once(fn)(只执行一次)
  • 用闭包实现 memoize(fn)(结果缓存)
  • 闭包在模块化(CommonJS/ESM)下的角色

10) 答题小技巧

  • 举例时说明"为什么 GC 不会回收该变量"。
  • 对 for+var 陷阱:先说结果,再给两种修复(let 与 IIFE)。
  • 若被追问性能:说明闭包会延长变量生命周期,应及时解除不必要的引用。

闭包(Closure)超精简速记卡片

专门用于快速复习、面试前突击、临场背诵。内容极度浓缩,但覆盖所有核心点,读一遍就能记住。

1. 闭包是什么?(最标准定义)

闭包 = 函数 + 其定义时的词法环境(Lexical Environment)。

即:函数能访问它外层作用域的变量,即使外层作用域已执行结束。

2. 为什么会有闭包?(一句话解释)

因为 JS 是词法作用域,并且内部函数对外层变量形成引用,导致外层变量不会被回收。

3. 闭包的作用(全部面试高频点)

  • 模拟私有变量 / 数据隐藏
  • 延长变量生命周期
  • 工厂函数(返回预配置函数)
  • 函数记忆(memoize)
  • 柯里化/部分应用(curry)
  • 异步回调中保持状态

4. 最典型的闭包例子(必背)

javascript 复制代码
function createCounter() {
  let count = 0;
  return function () {
    return ++count;
  };
}

5. for + var 经典面试题(必考)

问题

javascript 复制代码
var arr = [];
for (var i = 0; i < 3; i++) {
  arr.push(() => console.log(i));
}
arr[0](); arr[1](); arr[2]();

输出: 3 3 3

原因: 三个函数共享同一个 var i,循环结束后 i = 3

修复方法(两种都要会)

  • 方法一: 换成 let

    javascript 复制代码
    var arr = [];
    for (let i = 0; i < 3; i++) {
      arr.push(() => console.log(i));
    }
  • 方法二: IIFE 捕获当前值

    javascript 复制代码
    var arr = [];
    for (var i = 0; i < 3; i++) {
      (function(j) {
        arr.push(() => console.log(j));
      })(i);
    }

6. 闭包常见问题(两句就够)

  • 闭包会让外层变量常驻内存 → 可能造成高内存占用
  • 及时释放:把引用设为 null 或让闭包脱离作用域

7. 面试标准作答模板(背下来就能稳过)

"闭包是函数与其词法环境的组合,它允许函数在外层作用域结束后仍能访问外层变量。

闭包常用于私有变量、工厂函数、柯里化和在异步回调中保持状态。

本质是内部函数对外层变量的引用未释放,因而外层变量的生命周期被延长。"

8. 面试进阶加分句(一句即可)

闭包的根本来源是 JS 的词法作用域规则 + 引擎对词法环境的持久化保存,而不是函数调用位置决定的。

相关推荐
j***63081 小时前
SpringbootActuator未授权访问漏洞
android·前端·后端
ze_juejin1 小时前
JavaScript事件循环总结
前端
forestsea1 小时前
现代 JavaScript 加密技术详解:Web Crypto API 与常见算法实践
前端·javascript·算法
_前端小李_1 小时前
pnpm老是默认把包安装在C盘很头疼?教你快速配置pnpm的全局目录
前端
Cache技术分享1 小时前
254. Java 集合 - 使用 Lambda 表达式操作 Map 的值
前端·后端
踏浪无痕1 小时前
手写一个Nacos配置中心:搞懂长轮询推送机制(附完整源码)
后端·面试·架构
CryptoPP1 小时前
使用 KLineChart 这个轻量级的前端图表库
服务器·开发语言·前端·windows·后端·golang
p***43482 小时前
前端路由管理
前端