470 字
2 分钟
05 · 闭包
当年最懵的概念
面试被问”什么是闭包”,我背了定义却答不出”它解决什么问题”。后来才懂:闭包让函数能访问定义时所在的作用域,即使那个作用域已执行完。
形成条件
function outer() { const count = 0; return function inner() { count++; return count; };}const fn = outer();fn(); // 1fn(); // 2inner 被返回后,outer 的作用域本该销毁,但因为 inner 还在引用 count,这块作用域被保留——这就是闭包。count 活在闭包里,成为”私有变量”。
经典坑:循环 + var
回顾 01 篇的例子,用闭包修复:
for (var i = 0; i < 3; i++) { ((j) => setTimeout(() => console.log(j), 0))(i); // 0 1 2}// 或现代写法:直接用 let(块级作用域天然隔离)for (let i = 0; i < 3; i++) setTimeout(() => console.log(i), 0);let 的方案本质也是每轮迭代形成独立作用域(类闭包),所以最推荐。
真实应用
- 模块封装:IIFE / 模块返回的对象方法构成闭包,隐藏私有状态(早期的”模块模式”)。
- 防抖/节流:闭包保存定时器 ID 和上一次时间。
- 函数工厂:根据参数”定制”返回的函数。
function debounce(fn, delay) { let timer; return (...args) => { clearTimeout(timer); timer = setTimeout(() => fn(...args), delay); };}小结
- 闭包 = 函数 + 其词法作用域;能”记住”外部变量。
- 它是模块私有化、高阶函数的基础。
- 循环问题用
let或 IIFE 解决。 - 闭包持有变量会延长生命周期,注意避免不必要的内存占用。
练习
- 用闭包实现一个
createCounter(),每次调用返回自增后的值。 - 写一个
once(fn),让函数只执行一次(闭包保存标志位)。 - 解释为什么下面返回 5 个 5,如何用
let改成 0-4:for (var i=0;i<5;i++){ setTimeout(()=>console.log(i),0); }。