闭包就是函数套函数,内层函数能记住外层的变量,即使外层已经执行完。
核心三步:
① 外层函数——定义变量
② 内层函数——使用外层变量
③ return 内层——把内层函数交出去,外部还能访问
坑:循环里用 var 做闭包全部输出最后一个值、闭包会占用内存、用 let 解决循环闭包问题。
| 要素 | |
|---|---|
| ① 外层函数 | 里面藏着一个私人变量 |
| ② 内层函数用了外层变量 | 小助理要碰你储物柜里的东西 |
| ③ 把内层 return 出去存住 | 外面用变量接住,外层跑完东西也不丢 |
function outer() {
// ① 外层的私人储物柜
let count = 0;
// ② 小助理 inner,要用这个柜子
function inner() {
count++;
console.log(count);
}
// ③ 把小助理交出去
return inner;
}
// outer跑完了,但fn接住了小助理
let fn = outer();
// 1 ← 柜子没丢,助理还记得
fn();
// 2 ← 接着往上加
fn();
// 雇第一个助理,给他一个柜子 let fn1 = outer(); // 雇第二个助理,另一个柜子 let fn2 = outer(); fn1(); // 1 2(fn1记自己的) fn1(); fn2(); // 1 2(fn2记自己的,各管各) fn2();
如果你想让它打印 0、1、2,结果它打印 3、3、3,就会一脸懵。因为 let i 是大家共用的同一个,等 100 毫秒后定时器真去读的时候,循环早就跑完,i 已经停在 3 了。
// 定时器100毫秒后才执行,但那时循环早跑完了
for (let i = 0; i < 3; i++) {
setTimeout(function () {
// 三个函数读的是同一个 i
console.log(i);
}, 100);
// 输出:3 3 3(i 最后停在 3)
}
for (let i = 0; i < 3; i++) {
// 每一轮立刻新开一个小隔间
(function (j) {
setTimeout(function () {
// 这个隔间锁死了当时的 j
console.log(j);
}, 100);
// 把当前 i 传进去当 j
})(i);
// 输出:0 1 2
}
好处:把私人变量藏起来(外面改不了)、能记住状态(计数器、缓存)。坏处:这些变量不会自动被清掉,一直占内存。用完要主动松手:fn = null;。
① 用在哪:做私有计数器、收藏夹开关、保存上次状态的小工具。
② 常见坑:循环里 var + 定时器打印出 333;闭包用完不释放一直占内存。
③ 怎么解决:循环里用 let,或 IIFE 把每次 i 锁进去;不用了 fn = null 释放。
| API | 作用 | 参数 | 返回值 | 代码示例 |
|---|---|---|---|---|
| 函数套函数 | 内层记住外层的私人变量 | 无 | 无 | function outer() {
function inner() {}
} |
| return 内层函数 | 把小函数交出去存住 | 函数 | 函数 | function outer() {
return function () {};
} |
| fn = null | 用完松手释放闭包 | 无 | 无 | fn = null; // 放走,才能回收 |