跳到主要内容

闭包

2025年03月19日
柏拉文
越努力,越幸运

一、普通函数闭包


function z(x){
return function(){
x = x+3;
console.log(x);
}
}
const zs = z(2);
zs(3); // 5
zs(5); // 8

二、对象函数闭包


const obj = {
z: (function (x) {
return function () {
x += 3;
console.log(x);
};
})(2),
};
obj.z(3); // 5
obj.z(5); // 8

一、立即执行函数表达式: 利用了立即执行函数表达式(IIFE)来创建一个闭包, 从而实现了私有变量的累加效果。在定义时立即调用, 将 2 传给参数 x。函数返回的匿名函数捕获了 x, 即形成了闭包。因此, 代码中的闭包保留了一个私有变量 x(初始值为 2), 每次调用 obj.z 时, 都会使 x 增加 3 并打印出来。传递给 obj.z 的参数(如 35)并未被使用。

二、函数调用与变量更新: 第一次调用 obj.z(3) 时, 初始 x 值为 2, 函数执行 x += 3, 因此 x 变为 5, 并打印 5。第二次调用 obj.z(5) 时, 此时 x 已经是 5, 函数再次执行 x += 3, 更新 x8, 并打印 8

三、for 循环与闭包 I


var data = [];

for (var i = 0; i < 3; i++) {
data[i] = function () {
console.log(i);
};
}

data[0]();
data[1]();
data[2]()

一、执行 for 循环, 变量 ivar 声明(var 声明的变量是函数级作用域, 而非块级作用域)。在循环体内, 每次迭代都把一个匿名函数赋值给 data[i]。这个匿名函数中使用了变量 i, 保存了外部 i 的引用。当循环结束后, i 的值为 3

二、执行闭包函数, 当调用 data[0](); data[1](); data[2](); 时, 所有闭包函数共享同一个变量 i, 当循环结束后 i 的值为 3, 因此调用每个函数时都会输出 3

那么, 如何避免这一个问题?:

  1. 使用 let 替换 var, 因为 let 是块级作用域, 每次循环都会创建一个新的 i 值:
for (let i = 0; i < 3; i++) {
data[i] = function () {
console.log(i);
};
}
  1. 或者使用立即执行函数表达式(IIFE)来捕获当前 i 的值:
for (var i = 0; i < 3; i++) {
(function(j) {
data[j] = function () {
console.log(j);
};
})(i);
}

四、for 循环与闭包 II


var result = [];
var a = 3;
var total = 0;

function foo(a) {
for (var i = 0; i < 3; i++) {
result[i] = function () {
total += i * a;
console.log(i, a, total);
};
}
}

foo(1);
result[0](); // 3 1 3
result[1](); // 3 1 6
result[2](); // 3 1 9

一、执行 foo 函数: 函数 foo 接受一个参数 a, 当调用 foo(1) 时, 函数内的 a(值为 1)会遮蔽全局的 a。因此在 foo 内部, a 始终为 1

二、foo 函数执行 for 循环: 在循环体内, 每次迭代都把一个匿名函数赋值给 result[i]。这个匿名函数中使用了变量 ia, 保存了外部 ia 的引用。当 for 循环结束后, i 的值已经变成了 3。所有匿名函数都捕获了同一个 i 变量, 因此它们在执行时都会使用 i = 3

三、执行闭包函数: 调用 foo(1) 后, result 数组中有三个闭包函数, 所有闭包函数共享同一个变量 i, 当循环结束后 i 的值为 3。而 a 始终为 1(函数参数)。每次调用匿名函数时, 都执行 total += i * a, 也就是 total += 3 * 1

那么, 如何避免这种问题?: 如果想要每个匿名函数捕获当前迭代的 i 值, 可以用 let 替换 var 声明 i, 因为 let 是块级作用域, 每次循环都会创建一个新的 i

for (let i = 0; i < 3; i++) {
result[i] = function () {
total += i * a;
console.log(i, a, total);
};
}

这样调用时会得到期望的 0, 1, 2 而不是全部为 3