发布于 2026-01-06 8 阅读
0

理解 JavaScript 中的闭包

理解 JavaScript 中的闭包

介绍

本文将介绍 JavaScript 中的闭包。

每个 JavaScript 开发人员都应该了解闭包,因为它是面试中最常见的问题,而且清楚地理解它将有助于避免在代码中产生错误。

那么,我们开始吧。

什么是结束?

在深入探讨闭包之前,我们首先需要了解 JavaScript 中的作用域是如何工作的。

请查看以下代码:

function display() {
 var i = 10;
 console.log(i); // 10
}

display();
console.log(i); // Uncaught ReferenceError: i is not defined
Enter fullscreen mode Exit fullscreen mode

当我们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
Enter fullscreen mode Exit fullscreen mode

在上面的代码中,我们在函数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
Enter fullscreen mode Exit fullscreen mode

在上面的代码中,变量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
Enter fullscreen mode Exit fullscreen mode

在上面的代码中,我们从函数返回一个对象,该对象具有函数属性,因此它也是一个闭包。

现在,请看下面的代码:

for(var i = 0; i < 5; i++) {
 setTimeout(function() {
  console.log(i);
 }, 1000);
}
Enter fullscreen mode Exit fullscreen mode

这是一个非常著名的面试题。

你可能认为上面的代码会打印 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);
}
Enter fullscreen mode Exit fullscreen mode

上述代码将正确打印出 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);
}
Enter fullscreen mode Exit fullscreen mode

现在,请看下面的代码:

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
Enter fullscreen mode Exit fullscreen mode

这里,我们每次得到的都是打印出来的值,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
Enter fullscreen mode Exit fullscreen mode

在上面的代码中,我们在函数getValue内部定义了函数,因此我们创建了一个闭包,它将保留传递给它log的值。i

因此,我们得到了预期的正确输出。

结论

本文就到这里。

希望你现在已经清楚如何在 JavaScript 中使用闭包了。

别忘了订阅我的每周新闻简报,直接在您的收件箱中接收精彩的技巧、窍门和文章。点击此处订阅。

文章来源:https://dev.to/myogeshchavan97/understanding-closures-in-javascript-1nnp