概念:在JavaScript中,闭包(Closure)是指一个函数有权利访问定义在它外部作用域的任何变量。
function outerFn(outerVal) {
return function innerFn(innerVal) {
console.log('outerVal', outerVal)
console.log('innerVal', innerVal)
}
}
const newFunction = outerFn('outside')
newFunction('inside')
// outerVal outside
// innerVal inside
内部函数有权利访问外部作用域的变量outerVal,而外部函数的变量已经被内部函数绑定。
使用场景
-
数据封装和私有化
创建私有变量:闭包可以帮助我们封装私有变量,防止外部直接访问和修改。
模块模式:使用闭包来实现模块化代码,每个模块都有自己的作用域,不会污染全局命名空间。function createCounter() { let val = 0; return { increment() { val++; }, getVal() { return val; } } } let counter = createCounter() counter.increment() console.log(counter.getVal())// 1 counter.increment() console.log(counter.getVal())// 2
-
柯里化(Currying)
通过闭包,可以创建一个函数,这个函数被调用时不会立即执行,而是返回一个新的函数,新函数可以访问到原函数的参数。
function curry(func) {
const len = func.length;
// 返回一个新函数,这个新函数会处理参数的收集
return function curried(...args) {
// 如果传入的参数数量足够,直接调用原始函数
if(args.length >= len) {
return func.apply(this, args)
} else {
// 如果参数数量不足,返回一个新的函数,这个函数继续收集参数
return function(...args2) {
return curried.apply(this, args.concat(args2))
}
}
}
}
function add(a,b,c) {
return a+b+c;
}
const curriedAdd = curry(add);
console.log(curriedAdd(1,2,3))// 6
console.log(curriedAdd(1)(2,3))// 6
console.log(curriedAdd(1,2)(3))// 6
-
高阶函数
高阶函数可以接收函数作为参数或将函数作为返回值,闭包使得这些函数可以访问并操作外部作用域的变量。
function formatNumberToLocaleString() {
return function(number) {
// 将数字转换为字符串,并使用正则表达式实现千分位分割
return number.toLocaleString();
};
}
const formatThousandSeparator = formatNumberToLocaleString();
// 使用闭包实例格式化数字
console.log(formatThousandSeparator(1234567.89)); // 输出 "1,234,567.89"
4.实现工厂函数
工厂函数可以创建并返回一个具有特定功能的对象,闭包可以用来保持每个对象的私有状态。
function carFactory(brand) {
// 私有变量
let engineState = 'off';
return {
startEngine: function() {
engineState = 'on';
console.log(`${brand} engine is now ${engineState}`)
},
stopEngine: function() {
engineState = 'off'
console.log(`${brand} engine is now ${engineState}`)
},
}
}
const ford = carFactory('ford')
ford.startEngine()// ford engine is now on
ford.stopEngine()// ford engine is now off
5.事件处理和异步操作
在事件处理或异步操作中,闭包可以用来保持对特定作用域的引用,即使是在回调函数执行时作用域已经消失。
// 假设有一个元素列表
const items = document.querySelectorAll('.item');
// 创建一个闭包来处理点击事件和异步操作
function createEventHandler(itemIndex) {
return function(event) {
// 从服务器获取数据
fetchDataFromServer(itemIndex).then(data => {
console.log(`Item ${itemIndex} data:`, data);
}).catch(error => {
console.error(`Error fetching data for item ${itemIndex}:`, error);
});
};
}
// 为每个项目添加事件监听器
items.forEach((item, index) => {
// 使用闭包来保持 itemIndex 的状态
item.addEventListener('click', createEventHandler(index));
});
// 模拟从服务器获取数据的异步函数
function fetchDataFromServer(itemIndex) {
return new Promise((resolve, reject) => {
setTimeout(() => {
// 模拟成功或失败的情况
if (Math.random() > 0.5) {
resolve(`Data for item ${itemIndex}`);
} else {
reject(new Error(`Failed to fetch data for item ${itemIndex}`));
}
}, 1000);
});
}
-
迭代器和生成器
生成器函数利用闭包来保持其在每次迭代时的状态。
function createArrayIterator(array) {
let index = 0;
return {
next: function() {
if (index < array.length) {
return { value: array[index++], done: false };
} else {
return { done: true };
}
}
};
}
const myArray = [1, 2, 3];
const iterator = createArrayIterator(myArray);
console.log(iterator.next()); // { value: 1, done: false }
console.log(iterator.next()); // { value: 2, done: false }
console.log(iterator.next()); // { value: 3, done: false }
console.log(iterator.next()); // { done: true }
闭包是JavaScript最好的语法之一。