7 分钟学会 JavaScript 闭包
要学习闭包,您需要了解范围的工作原理。
在 JavaScript 中,我们有全局和本地范围。
在主程序中声明的变量称为全局变量。它们属于全局对象,可以在代码中的任何位置访问。
在函数中声明的变量称为局部作用域。它们属于函数体(包括其嵌套函数),并且可以访问全局作用域中的任何变量。
如果一个函数定义在另一个函数内部,则父函数将无法访问子函数中声明的变量。但子函数可以访问父函数中的变量。
因此基本上任何代码块都可以访问其外部范围的变量。
这是一个例子
const x = 'someone';
function incrementFrom(count) {
// has access to x
return count++;
}
const firstCall = incrementFrom(0);
const secondCall = incrementFrom(5);
console.log(firstCall);
console.log(secondCall);
// does not have access to count i.e console.log(count) will throw an error
请记住,该参数是局部变量,因为它位于局部作用域内。每次调用该incrementFrom
函数时,它都会重新创建。
它基本上等同于
function incrementNum() {
let count = 5;
return count++;
}
// all the calls
因此好消息是,局部变量在函数调用时不会相互践踏。
但坏消息是,这种标准调用incrementFrom(5)
几次并不会使其增加。它只会继续记录 5,因为“每个局部变量在每次调用时都会重新创建”。
那么,如果我们想在每次调用函数时都使用传入(或创建)的值,该怎么办呢?就像在 的情况下incrementFrom()
,我们只想获取一个初始值,并在每次调用时递增它。
因此,当我调用incrementFrom(3)
3 次时,它会神奇地从 3 增加到 4,然后增加到 5,再增加到 6。这可以通过闭包实现。
另一个例子可能是将用户的 传递firstName
给函数,然后将 添加lastName
到其中。例如
function printName(firstName, lastName) {
return `${firstName} ${lastName}`;
}
由于某些原因,lastName
尚未提供,因此您使用现有资源进行第一次调用
console.log(printName('John', "I don't have it yet"));
最后,lastName
获取被处理了,现在内存中没有地方了firstName
,所以你最终会这样做来使用进行第二次调用lastName
。
console.log(printName('I lost it', 'Doe'));
我知道这个例子有点傻,但其核心思想是进行两次函数调用,并将它们的局部变量关联起来。这可以通过闭包实现。
那么,什么是闭包?
在 Eloquent JavaScript 中它说
...能够在封闭范围内引用本地绑定的特定实例称为闭包。
简而言之,闭包是即使外部函数已经关闭(不再活动),仍然可以访问外部函数范围的函数。
这意味着子函数可以随时使用父函数中声明的任何局部变量,即使父函数已被调用并且不再处于活动状态。
它的工作方式是这样的,当我们创建一个带有任何局部变量的函数时,该函数将返回另一个函数(即子函数),并且如上所述,子函数可以访问父函数中的变量。
所以当函数被调用时,其值是一个函数,可以被调用。即
function callMe() {
return () => 'Hello world';
}
const funcVal = callMe();
console.log(funcVal());
这只是“函数就是它们返回的内容”的一种表达,或者更好地表达为“函数作为值”。
因此,当调用返回字符串的函数时,字符串的属性和方法可以在该函数调用中使用。数字、数组、对象和函数也一样。
在这种情况下,我们的函数返回一个函数,这意味着callMe()
可以调用该函数的值,因为它是一个函数(您可以添加 params 和 args)。
这就是事情变得更加有趣的地方……
function callMe(val) {
return (newVal) => val + newVal;
}
const funcVal = callMe(2);
console.log(funcVal(2)); // 4
我们已经调用了callMe()
一次函数并传入了一个值。现在,当我们调用它返回的函数时,可以使用这个值。这就是闭包。
我现在可以调用funcVal()
不同的时间,它仍然可以访问val
父函数的局部变量()( callMe
)
console.log(funcVal(3)); // 5 i.e 2 + 3
console.log(funcVal(10)); // 12 i.e 2 + 10
// we can go on and on
现在,函数局部变量在不同调用中不践踏自身的规则仍然有效,我们只对父函数进行了一次调用callMe
,让我们尝试再调用一次
const funcVal = callMe(2);
const funcVal2 = callMe(100); // local variable (val) will be created anew here with a value of 100.
console.log(funcVal(2)); // 4 i.e 2 + 2
console.log(funcVal2(10)); // 110 i.e 100 + 10
所以基本上,它们返回的函数才是真正的魔法。即便如此,它们的局部变量在不同的调用中仍然不会互相干扰。
console.log(funcVal(3)); // 5 i.e 2 + 3
console.log(funcVal(10)); // local variable (newVal) will be created anew here, but it still has access to the local variables in the outer function. so we get 12 i.e 2 + 10
现在让我们回到最初的例子或问题。我们先解决名称问题。
回想一下,我们之前有一个函数printName
可以打印用户的名字和姓氏,但由于某种原因,姓氏会被延迟(我们知道这一点),所以我们一开始只能先不打印它。等它最终打印出来后,我们应该打印全名。我们会这样做:
function printName(firstName) {
return (lastName) => `${firstName} ${lastName}`;
}
现在功能有一点改变
- 该函数
printName
现在只接受一个参数(firstName
-我们知道那个人不会被延迟) - it(
printName
) 现在返回一个函数而不是返回一个字符串。 - 子函数接受
lastName
(我们知道将会延迟的人)然后返回全名字符串。
如果我们尝试记录,现在会更有意义
// first name comes
const user = printName('John');
// after a while, last name comes
console.log(user('Doe')); // John Doe
瞧!问题用闭包解决了。我们来添加另一个用户吧。
// first name comes
const user = printName('John');
// after a while, last name comes
console.log(user('Doe')); // John Doe
// new user
const user2 = printName('Sarah');
console.log(user2('Michelle')); // Sarah Michelle
我知道有很多其他方法可以解决这个问题,但这是另一种方法。
现在,在我们结束本文之前,最后一个例子是我们的计数器。
回想一下,我们有一个函数incrementFrom
,它没有任何递增。我们该如何解决这个问题?
function incrementFrom(count) {
return () => count++;
}
只有一件事发生了变化,我们返回了一个返回的函数,count + 1
而不是仅仅返回count + 1
。
现在让我们看看它是否有效
const addOne = incrementFrom(5);
console.log(addOne()); // 5
console.log(addOne()); // 6
console.log(addOne()); // 7
console.log(addOne()); // 8
// and on and on
令人高兴且毫不意外的是,它有效!!
这就是闭包在编程中非常有用的原因。
结论
如果你是第一次学习闭包,那么这部分内容可能有点难理解。但随着练习的深入,你就会逐渐明白。
感谢您读到最后,希望您和我一样享受其中并从中学习。下次再见。与此同时,您可以给我留言,分享您的想法。您也可以点击“赞”和“分享”按钮,以便我们联系到更多开发者。
让我们联系吧,在 Twitter 上联系我@elijahtrillionz
文章来源:https://dev.to/elijahtrillionz/learn-javascript-closures-in-7-mins-324n