← 返回目录

12. 闭包原理与循环坑

闭包是面试必问、也是最绕的概念。大白话:函数里再套个函数,这个小函数还一直记着外面的变量。打个比方:你的私人储物柜(外层变量)锁在办公室里,别人进不来;但你的助理(内层函数)能帮你存取。你出差走了(外层执行完了),助理还在,储物柜的东西也还没丢。

12.1 互动演示(闭包计数器 + 循环 var 打印 333 坑)

点「+1」看数字记住了没有;再看循环闭包那个著名的 333 坑。

12.2 知识点逐条讲解(配合代码)

① 闭包三要素(少一个都不算)

要素大白话
① 外层函数里面藏着一个私人变量
② 内层函数用了外层变量小助理要碰你储物柜里的东西
③ 把内层 return 出去存住外面用变量接住,外层跑完东西也不丢
function outer() {
    let count = 0;          // ① 外层的私人储物柜
    function inner() {      // ② 小助理 inner,要用这个柜子
        count++;
        console.log(count);
    }
    return inner;           // ③ 把小助理交出去
}
let fn = outer();           // outer跑完了,但fn接住了小助理
fn();   // 1  ← 柜子没丢,助理还记得
fn();   // 2  ← 接着往上加

	

② 多雇几个助理,各记各的账

let fn1 = outer();   // 雇第一个助理,给他一个柜子
let fn2 = outer();   // 雇第二个助理,另一个柜子
fn1(); fn1();   // 1 2(fn1记自己的)
fn2(); fn2();   // 1 2(fn2记自己的,各管各)

	

③ 著名坑:for + var 闭包打印 3 3 3

如果你想让它打印 0、1、2,结果它打印 3、3、3,就会一脸懵。因为 var i 是大家共用的同一个,等 100 毫秒后定时器真去读的时候,循环早就跑完,i 已经停在 3 了。

for (var i = 0; i < 3; i++) {
    // 定时器100毫秒后才执行,但那时循环早跑完了
    setTimeout(function () {
        console.log(i);   // 三个函数读的是同一个 i
    }, 100);
}
// 输出:3 3 3(i 最后停在 3)

	

④ 修复:IIFE 立即函数,把每一轮的 i 单独锁起来

for (var i = 0; i < 3; i++) {
    (function (j) {            // 每一轮立刻新开一个小隔间
        setTimeout(function () {
            console.log(j);    // 这个隔间锁死了当时的 j
        }, 100);
    })(i);                     // 把当前 i 传进去当 j
}
// 输出:0 1 2

	

⑤ 优缺点与释放

好处:把私人变量藏起来(外面改不了)、能记住状态(计数器、缓存)。坏处:这些变量不会自动被清掉,一直占内存。用完要主动松手:fn = null;

⑥ X.3 实战:用在哪 / 常见坑 / 怎么解决

① 用在哪:做私有计数器、收藏夹开关、保存上次状态的小工具。

② 常见坑:循环里 var + 定时器打印出 333;闭包用完不释放一直占内存。

③ 怎么解决:循环里用 let,或 IIFE 把每次 i 锁进去;不用了 fn = null 释放。

一句话记住:闭包=函数套函数、小函数记住外面的变量、外层跑完变量也不丢;用来做私人计数器;for+var 闭包会打印 333,用 IIFE 把每轮 i 锁进自己的隔间;用完 fn=null 松手