闭包(Closure)是 JavaScript 中的一个重要概念,它可以让你访问并保持在函数外部定义的变量。
权威指南 :闭包指的是那些引用了另一个函数作用域中变量的函数,通常是在嵌套函数中实现的 MDN:闭包是由捆绑起来(封闭的)的函数和函数周围状态(词法环境)的引用组合而成。换言之,闭包让函数能访问它的外部作用域。在 JavaScript 中,闭包会随着函数的创建而同时创建。
作用域:函数作用域,全局作用域和块作用域(ES6) 作用域链:不同作用域嵌套在一起,形成作用域链,查找方向由内到外
闭包主要解决以下几个问题:
1. 数据的私有化和封装
闭包可以让我们创建“私有变量”,通过在函数内部定义变量并返回一个操作这些变量的函数,外部代码无法直接访问这些变量,只能通过特定的接口进行操作。这是实现数据封装的常用手段。
示例:私有变量
function createCounter() {
let count = 0; // 私有变量
return function() {
count++;
console.log(count);
};
}
const counter = createCounter();
counter(); // 输出 1
counter(); // 输出 2
counter(); // 输出 3
在这个例子中,count
变量是函数 createCounter
内部的私有变量,它只能通过返回的闭包函数访问和修改。外部无法直接访问或修改 count
,从而实现了对数据的封装和保护。
2. 柯里化函数
柯里化(Currying): 把接受多个参数的函数变换成接受一个单一参数(或部分)的函数,并且返回接受余下的参数而且返回结果的新函数的技术。
// 单独实现第一种
function add (a, b) {
return a + b;
} // ...我为啥要写一遍,这个貌似白痴都会
// 单独实现第二种, 这也是最最最简单的柯里化,但并不完整
function add (a) {
return function(b) {
return a + b
}
} // 这里其实就是一个闭包,内部函数掉用了 b 自由变量, add(1)(2) --> 3
// 两种皆实现,就需要判断函数的参数值
function add (a, b) {
return arguments.length === 1 ? (b) => a + b : a + b
} // add(1,2) | add(1)(2) --> 3
3. 模拟块级作用域(在 ES6 之前)
在 ES6 之前,JavaScript 没有块级作用域,所有变量的作用域都是基于函数的。闭包可以用来模拟块级作用域,避免变量在全局或父函数作用域中被污染。
示例:闭包模拟块级作用域
for (var i = 1; i <= 3; i++) {
(function(i) {
setTimeout(function() {
console.log(i);
}, i * 1000);
})(i);
}
在这个例子中,每次迭代都会创建一个立即执行函数(IIFE),并传入当前的 i
。通过闭包,每个 setTimeout
的回调函数都会保持对不同 i
值的引用,从而避免 i
在循环结束后全变成最后一个值(4)。
4. 回调函数
function c(callback) {
callback(6, 5);
}
c((a, b) => {
//这里回调之后的操作
console.log(a + b); //11
});
5. IIFE
(function () {
// …
})();
}
闭包的总结
闭包解决了多个重要问题,包括:
- 数据封装与私有化:通过闭包,可以将数据和操作封装起来,外部无法直接修改内部状态,提升代码的安全性和可维护性。
- 保存函数的执行上下文:闭包能够保留函数执行时的上下文环境,确保异步执行或回调时仍然能够访问到函数内部的变量。
- 延迟执行与回调函数的状态保持:闭包在异步操作中非常有用,可以让回调函数记住特定的状态或变量。
- 模块化:闭包是实现模块化编程的重要工具,通过它可以实现私有变量和公开接口的分离。
闭包是 JavaScript 函数式编程的基础,它在各种场景中广泛应用,尤其是需要持久化某些数据或在函数执行后仍然需要访问特定变量的场景。