JavaScript Closure 简明解释
闭包是一个有状态的函数,它由另一个函数返回。它充当容器,即使父函数已经执行完毕,它仍然会保留父函数作用域中的变量和参数。考虑以下简单示例。
function sayHello() {
const greeting = "Hello World";
return function() { // anonymous function/nameless function
console.log(greeting)
}
}
const hello = sayHello(); // hello holds the returned function
hello(); // -> Hello World
看!我们有一个返回函数的函数!返回的函数会被保存到一个变量中,并在下面的代码行中被调用。
编写相同代码的方法有很多种!
现在你已经对闭包的基本概念有所了解,下面介绍几种编写与上面相同代码的方法。
// original
function sayHello() {
const greeting = "Hello World";
return function() { // anonymous function
console.log(greeting)
}
}
// #1
function sayHello() {
const greeting = "Hello World";
return function hello() { // named function
console.log(greeting)
}
}
// #2
function sayHello() {
const greeting = "Hello World";
function hello() { // named function
console.log(greeting)
}
return hello; // return inner function on a different line
}
// #3
function sayHello() {
const greeting = "Hello World";
const hello = () => { // arrow function
console.log(greeting)
}
return hello;
}
选择你最喜欢的样式并坚持使用,因为以上所有变体都会打印出相同的结果!
const hello = sayHello();
hello(); // -> Hello World
关闭的好处及其实际应用
私有命名空间
内部函数能够记住创建时的环境固然很棒,但它有什么用呢?有两个用途。首先,它可以保护你的变量是私有的。这里有一个经典的计数器示例。
function counter() {
let count = 0;
return function() {
count += 1;
return count;
}
}
const increment = counter();
console.log(increment()); // 1
console.log(increment()); // 2
console.log(count) // Reference error: count is not defined
尝试访问计数变量会导致引用错误,因为它没有暴露给全局环境。这有助于我们减少错误,因为我们的状态由特定方法更严格地控制。
可重用状态
因为“count”是私有作用域的,所以我们可以创建不同的计数器函数实例,而它们的“count”变量不会重叠!
function counter() {
let count = 0;
return function() {
count += 1;
return count;
}
}
const incrementBananaCount = counter();
const incrementAppleCount = counter();
console.log(incrementBananaCount()); // 1
console.log(incrementBananaCount()); // 2
console.log(incrementAppleCount()); // 1
模块设计模式
模块设计模式是构建 JavaScript 应用时常用的一种约定。它利用立即调用函数表达式 (IIFE) 返回对象,并且只公开你想要公开的变量和方法。
let Dog1 = (function() {
let name = "Suzy";
const getName = () => {
return name;
}
const changeName = (newName) => {
name = newName;
}
return {
getName: getName,
changeName: changeName
}
}())
console.log(name); // undefined
Dog1.getName() // Suzy
Dog1.changeName("Pink")
Dog1.getName() // Pink
这段代码一旦运行,函数就会执行并返回一个对象,该对象会被保存到 Dog1 中。这种模式回归到保持命名空间私有,仅通过对象的形式公开我们需要的内容,即公共方法和变量。状态被完全封装了!
著名的面试问题
运行以下函数会得到什么结果?
for(var i=0; i<5; i++) {
setTimeout(function() {
console.log(i)
}, 1000)
}
为什么这是个热门面试题?因为它考察你对函数作用域/代码块作用域、闭包、setTimeout 和匿名函数的理解!答案会在 1 秒后打印出五个 5。
5
5
5
5
5
怎么做到的呢?简单来说,`setTimeout` 函数会在循环中每隔 1 秒执行 5 次。超时后,循环内部的函数会执行,这些函数会简单地输出 `i` 的值。1 秒过后,循环已经结束,`i` 的值变成了 5。于是,循环输出了五个 5。这跟你预想的不一样?你可能想看到的是 `i` 的值依次从 1 到 5。
解决方案
有一些解决方法,但我们先重点关注使用闭包!
for(var i=0; i<5; i++) {
setTimeout((function(index) {
return function() {
console.log(index);
}
}(i)), 1000)
}
我们有一个闭包,它由一个匿名函数返回,该函数接收当前的“i”作为参数,并将其作为“index”输出。这样做可以将当前变量 i 传递给每个函数。结果是:
0 (...1000ms have passed)
1 (...1000ms have passed)
2 (...1000ms have passed)
3 (...1000ms have passed)
4 (...1000ms have passed)
5 (loop exits)
恭喜!🎉🎉 现在你为下一次面试做好了更充分的准备!😉 记住,闭包是一个可以访问包含该函数的作用域的函数。
文章来源:https://dev.to/shimphillip/javascript-closure-simply-explained-1f79