
JavaScript 闭包
您将了解 JavaScript 闭包以及如何更有效地在代码中使用闭包
在本教程中,您将了解 JavaScript 闭包以及如何更有效地在代码中使用闭包。
JavaScript 闭包简介
在 JavaScript ,闭包是一个函数,它从其内部作用域引用外部作用域中的变量。闭包将外部作用域保留在其内部作用域内。
要理解闭包,您需要先了解词法作用域是如何工作的。
词法作用域
词法作用域定义了变量的作用域,而变量的作用域是通过变量在源代码声明的位置来决定。例如:
let name = 'John';
function greeting() {
let message = 'Hi';
console.log(message + ' '+ name);
}在这个例子中:
name变量是一个全局变量。它可以从任何地方访问,包括在greeting()函数内。message变量是一个局部变量,只能在greeting()函数内访问。
如果你试图在 greeting() 函数外访问的 message 变量,你会得到一个错误。所以 JavaScript 引擎使用作用域来管理变量的可访问性。
根据词法作用域,作用域可以嵌套,内部函数可以访问在其外部作用域声明的变量。例如:
function greeting() {
let message = 'Hi';
function sayHi() {
console.log(message);
}
sayHi();
}
greeting();greeting() 函数创建一个名为 message 的局部变量和一个名为 sayHi() 的函数。
内部函数 sayHi() 是仅在 greeting() 函数体内可用 。sayHi() 函数可以访问外部函数的变量,例如greeting() 函数的 message 变量。
在 greeting() 函数内部,我们调用 sayHi() 函数来显示消息 Hi。
JavaScript 闭包
让我们修改 greeting() 函数:
function greeting() {
let message = 'Hi';
function sayHi() {
console.log(message);
}
return sayHi;
}
let hi = greeting();
hi(); // still can access the message variable现在,greeting() 函数返回 sayHi() 函数对象,而不是在函数 greeting() 内部执行 sayHi() 函数。
请注意,函数是 JavaScript 一等公民,因此,您可以从另一个函数返回一个函数。
在 greeting() 函数之外,我们将 greeting() 函数返回的值赋给变量 hi,这是 sayHi() 函数的引用。
然后我们使用 sayHi() 函数的引用执行函数 hi() 。如果你运行代码,你会得到和上面一样的效果。
然而,这里有趣的一点是,通常情况下,局部变量仅在函数执行期间存在。这意味着当 greeting() 函数完成执行时,message 变量将不再可访问。
在这种情况下,我们执行 sayHi() 引用函数的 hi() 函数,message 变量仍然存在。
其神奇之处在于闭包。换句话说,sayHi() 函数是一个闭包。闭包是将外部作用域保留在其内部作用域中的函数。
JavaScript 闭包示例
下面的例子说明一个实际的闭包例子。
function greeting(message) {
return function(name){
return message + ' ' + name;
}
}
let sayHi = greeting('Hi');
let sayHello = greeting('Hello');
console.log(sayHi('John')); // Hi John
console.log(sayHello('John')); // Hello Johngreeting() 函数接受一个参数 message 并返回一个函数,该函数接受一个 name 参数。
返回的函数返回一条问候消息,它是 message 和 name 变量的组合。
greeting() 函数的行为类似于函数工厂。它使用相应的消息和来创建 sayHi()和 sayHello()运行。
sayHello() 和 sayHi() 是闭包。它们共享相同的函数体,但存储者不同的作用域。
在 sayHi() 闭包中,message 是 Hi,而在 sayHello() 闭包中, message 是 Hello。
JavaScript 闭包在循环
考虑以下示例:
for (var index = 1; index <= 3; index++) {
setTimeout(function () {
console.log('after ' + index + ' second(s):' + index);
}, index * 1000);
}输出:
after 4 second(s):4
after 4 second(s):4
after 4 second(s):4代码显示相同的消息。
我们想在循环迭代中每次复制 i 的值,以便在 1、2、3 秒后显示一条消息。
您在 4 秒后看到相同消息的原因是 setTimeout() 回调函数是一个闭包。i 它会记住循环最后一次迭代的值,即 4。
此外,for 循环创建所有三个闭包共享相同的全局作用域并访问相同的 i 值。要解决此问题,您需要在循环的每次迭代中创建一个新的闭包作用域。
有两种流行的解决方案:IIFE 和 let 关键词。
使用 IIFE 解决方案
在此解决方案中,您使用一个立即调用函数表达式(也称为 IIFE),因为 IIFE 通过声明一个函数并立即执行它来创建一个新的作用域。
for (var index = 1; index <= 3; index++) {
(function (index) {
setTimeout(function () {
console.log('after ' + index + ' second(s):' + index);
}, index * 1000);
})(index);
}输出
after 1 second(s):1
after 2 second(s):2
after 3 second(s):3在 ES6 中使用 let 关键词
在 ES6 中,您可以使用 let 关键词来声明块作用域的变量。
如果您在 for 循环中使用 let 关键字,它将在每次迭代中创建一个新的词法作用域。换句话说,您将在每次迭代中拥有一个新的 index 变量。
此外,新的词法作用域被链接到先前的作用域,以便将 index 的先前值从先前的作用域复制到新的作用域。
for (let index = 1; index <= 3; index++) {
setTimeout(function () {
console.log('after ' + index + ' second(s):' + index);
}, index * 1000);
}输出
after 1 second(s):1
after 2 second(s):2
after 3 second(s):3结论
- 词法作用域描述 JavaScript 引擎如何使用变量的位置来确定该变量的可用位置。
- 闭包是函数,闭包函数具有可以记住外部作用域变量的能力。












