闭包
一、普通函数闭包
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
的参数(如 3
或 5
)并未被使用。
二、函数调用与变量更新: 第一次调用 obj.z(3)
时, 初始 x
值为 2
, 函数执行 x += 3
, 因此 x
变为 5
, 并打印 5
。第二次调用 obj.z(5)
时, 此时 x
已经是 5
, 函数再次执行 x += 3
, 更新 x
为 8
, 并打印 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
循环, 变量 i
用 var
声明(var
声明的变量是函数级作用域, 而非块级作用域)。在循环体内, 每次迭代都把一个匿名函数赋值给 data[i]
。这个匿名函数中使用了变量 i
, 保存了外部 i
的引用。当循环结束后, i
的值为 3
二、执行闭包函数, 当调用 data[0](); data[1](); data[2]();
时, 所有闭包函数共享同一个变量 i
, 当循环结束后 i
的值为 3
, 因此调用每个函数时都会输出 3
。
那么, 如何避免这一个问题?:
- 使用
let
替换var
, 因为let
是块级作用域, 每次循环都会创建一个新的i
值:
for (let i = 0; i < 3; i++) {
data[i] = function () {
console.log(i);
};
}
- 或者使用立即执行函数表达式(
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]
。这个匿名函数中使用了变量 i
和 a
, 保存了外部 i
和 a
的引用。当 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
。