理解 JavaScript 中的闭包
介绍
本文将介绍 JavaScript 中的闭包。
每个 JavaScript 开发人员都应该了解闭包,因为它是面试中最常见的问题,而且清楚地理解它将有助于避免在代码中产生错误。
那么,我们开始吧。
什么是结束?
在深入探讨闭包之前,我们首先需要了解 JavaScript 中的作用域是如何工作的。
请查看以下代码:
function display() {
var i = 10;
console.log(i); // 10
}
display();
console.log(i); // Uncaught ReferenceError: i is not defined
当我们var/let/const在函数内部使用关键字声明一个变量时,该变量仅在该函数内部可用,这意味着它的作用域仅限于该函数,因为它变成了一个私有变量。
如果你执行上面的代码,你会看到在函数内部,console.log由于变量i在函数作用域内可用,因此会打印值 10。
但在函数外部,该变量将不存在,因此当我们尝试打印它时,Uncaught ReferenceError会显示错误信息。
现在,请看下面的代码:
function add(x) {
var sum = 5;
function result(y) {
sum = sum + (x * y);
return sum;
}
return result;
}
var output = add(4); // output will contain the result function
console.log(output(5)); // 25
console.log(output(10)); // 65
在上面的代码中,我们在函数result内部定义了一个函数add,并且从该add函数中返回了该result函数。
所以,在第一个例子中,console.log我们得到的25输出是 5,因为sum在函数内部初始化为 5,所以 5 + (4 * 5) = 25。
当我们再次调用存储在变量中的函数output并传递 10 时,即使该函数已经执行完毕,它仍然会保留上次计算的变量sum值25,因此输出将是 65,因为 25 + (4 * 10) = 65。sum
所以,接下来就是闭包的定义。
当一个函数定义在另一个函数内部,并且由外部函数返回时,即使外部函数终止,该函数仍将保留对定义内部函数时作用域内所有变量的访问权限。
简而言之,在上面的代码中,当我们定义result函数时,它就可以访问该sum变量,并且我们result从函数中返回该函数,add因此即使函数执行完毕,该result函数仍将保留对该变量的访问权限。sumadd
这被称为闭包。
闭包非常有用,因为它提供了一种创建私有变量的方法,这些变量只能从函数内部访问,并且在函数调用之间会保留其值。
因此,我们不能sum直接访问该变量,只能通过调用result函数来访问,从而避免滥用该sum变量。
请查看以下代码:
function getCharacter(name) {
let i = 0;
return function next() {
const value = name[i++];
return value;
}
}
const next = getCharacter('hello');
console.log(next()); // h
console.log(next()); // e
console.log(next()); // l
console.log(next()); // l
console.log(next()); // o
在上面的代码中,变量i将在多次函数调用中保持其值,因此我们可以使用闭包在每次函数调用中获取每个字符。
我们可以将上述代码重写如下:
function getCharacter(name) {
let i = 0;
return {
next: function() {
const value = name[i++];
return value;
}
};
}
const obj = getCharacter('hello');
console.log(obj.next()); // h
console.log(obj.next()); // e
console.log(obj.next()); // l
console.log(obj.next()); // l
console.log(obj.next()); // o
在上面的代码中,我们从函数返回一个对象,该对象具有函数属性,因此它也是一个闭包。
现在,请看下面的代码:
for(var i = 0; i < 5; i++) {
setTimeout(function() {
console.log(i);
}, 1000);
}
这是一个非常著名的面试题。
你可能认为上面的代码会打印 0 到 4 之间的数字,但实际上它会打印 5 五次数字 5。
这是因为我们在 for 循环中使用了函数,所以当函数执行完毕(1 秒后)setTimeout时,for 循环已经执行完毕。setTimeout
i因此,当 for 循环结束时,x的值为5,所以该setTimeout函数将打印值 5 五次。
解决这个问题有两种方法。
- 使用闭包:
for(var i = 0; i < 5; i++) {
(function(i) {
setTimeout(function() {
console.log(i);
}, 1000)
})(i);
}
上述代码将正确打印出 0 到 4 的值。
这是因为通过将调用包装setTimeout在一个函数中,我们为每次交互创建了一个独特的作用域。
因此,我们传递给内部函数的值(i)将作为参数传递给该函数function(i)。
- 使用 let:
ES6 添加了一个let关键字,当在循环中使用时,它会为每次迭代创建一个新的作用域,因此我们可以直接使用let关键字,而不是使用,这样var就能正确地打印出 0 到 4 之间的值。
for(let i = 0; i < 5; i++) {
setTimeout(function() {
console.log(i);
}, 1000);
}
现在,请看下面的代码:
var numbers = {};
for(var i = 0; i < 10; i++){
numbers[i] = function() {
console.log(i);
}
}
console.log(numbers[0]()); // 10
console.log(numbers[1]()); // 10
console.log(numbers[2]()); // 10
这里,我们每次得到的都是打印出来的值,10而不是变量的实际值i。
我们可以通过两种方式解决这个问题。
- 使用
let代替var - 在循环外定义函数,如下所示:
var numbers = {};
function log(value) {
return function getValue() {
return value;
}
}
for(var i = 0; i < 10; i++){
numbers[i] = log(i);
}
console.log(numbers[0]()); // 0
console.log(numbers[1]()); // 1
console.log(numbers[2]()); // 2
在上面的代码中,我们在函数getValue内部定义了函数,因此我们创建了一个闭包,它将保留传递给它log的值。i
因此,我们得到了预期的正确输出。
结论
本文就到这里。
希望你现在已经清楚如何在 JavaScript 中使用闭包了。
别忘了订阅我的每周新闻简报,直接在您的收件箱中接收精彩的技巧、窍门和文章。点击此处订阅。
文章来源:https://dev.to/myogeshchavan97/understanding-closures-in-javascript-1nnp
