闭包在JavaScript编程中有多种实用场景,以下列举几个常见的闭包使用场景并附上相应的代码示例:
不使用 new
关键字调用构造函数时,构造函数内部的 this
关键字会指向全局对象(在浏览器中通常是 window
对象)。此时,构造函数的返回值取决于函数体中的代码逻辑。如果函数体中没有显式地返回一个对象,则返回 undefined
。
function Counter() {let count = 0; // 私有变量const aaa = () => {console.log(count);// return `1221`;};// 公共接口(闭包)return {bbb: function () {count++;},aaa: aaa, // 将aaa函数作为属性返回};
}
const counter = Counter();
counter.aaa(); // 0 只是执行了
console.log(counter.aaa(), "[["); //0 undefined [[
// 执行后输出0, 并没有返回值输出undefined, 最后输出[[
console.log(counter.count); //undefined 私有变量,没有返回,无法直接访问
0
0 undefined [[
undefined
使用class来改造,很明显不需要return就可以访问
使用 new
关键字调用构造函数时,会创建一个新的对象,并将该对象作为 this
关键字的值传递给构造函数。然后构造函数会在这个新对象上添加属性和方法,并最终将该对象作为返回值返回。这样创建的对象是构造函数的一个实例,具有构造函数所定义的属性和方法。
class Counter {constructor() {this.count = 0; // 私有变量}increment() {this.count++;}decrement() {this.count--;}getCount() {return this.count;}
}const counterInstance = new Counter();
counterInstance.increment();
console.log(counterInstance.getCount()); // 输出: 1
1. 模块化开发与私有变量封装
闭包可以用来模拟私有变量和方法,实现模块化的封装。外部代码无法直接访问到闭包内部的变量,只能通过提供的公共接口进行交互。
function Counter() {let count = 0; // 私有变量// 公共接口(闭包)return {increment: function () {count++;},decrement: function () {count--;},getCount: function () {return count;},};
}const counterInstance = Counter();
counterInstance.increment();
console.log(counterInstance.getCount()); // 输出: 1
下面是soybean-admin自己封装的useBoolean:
import { ref } from 'vue';/*** boolean组合式函数* @param initValue 初始值*/
export default function useBoolean(initValue = false) {const bool = ref(initValue);function setBool(value: boolean) {bool.value = value;}function setTrue() {setBool(true);}function setFalse() {setBool(false);}function toggle() {setBool(!bool.value);}return {bool,setBool,setTrue,setFalse,toggle};
}
2. 延长变量生命周期
闭包可以保持对外部函数作用域中变量的引用,即使外部函数已经执行完毕,这些变量也不会被垃圾回收机制回收,从而延长了它们的生命周期。
function createMultiplier(factor) {return function(number) {return number * factor;};
}const double = createMultiplier(2);
const triple = createMultiplier(3);console.log(double(5)); // 输出: 10
console.log(triple(5)); // 输出: 15
在这个例子中,createMultiplier
函数虽然已经执行完毕,但其内部创建的闭包(返回的匿名函数)仍然保持着对外部函数参数 factor
的引用,因此 double
和 triple
分别保留了不同的乘数因子。
3. setTimeout、setInterval 中的异步操作与传参
原生的 setTimeout
和 setInterval
函数不能直接捕获外部作用域中的变量,通过闭包可以解决这个问题,确保回调函数访问到正确的变量值。
function delayedAlert(message, delay) {setTimeout(function() {alert(message);}, delay);
}let user = "John";
delayedAlert(user + " will see this alert in 2 seconds.", 2000);// 即使在这之后 user 变量被改变,延迟的 alert 仍显示原始值
user = "Jane";
4. 事件监听器与回调函数
在处理事件绑定或异步操作时,闭包有助于确保回调函数能够访问到正确的上下文数据。
function attachEventHandlers(elements, eventName, handlerCreator) {elements.forEach((element) => {element.addEventListener(eventName, handlerCreator(element));});
}const buttons = document.querySelectorAll('button');
const logButtonId = function(button) {return function(event) {console.log(`Clicked button with id: ${button.id}`);};
};attachEventHandlers(buttons, 'click', logButtonId);
在这个例子中,每个按钮的点击事件处理器(闭包)都绑定了对应的按钮元素,即使所有处理器共享同一个事件处理逻辑(logButtonId
函数),也能正确地输出各自按钮的 ID。
5. 遍历数组或其他集合时的迭代状态
闭包可以用于记录迭代过程中的状态,特别是在使用高阶函数(如 Array.prototype.map
, Array.prototype.reduce
)时。
function getRunningTotal(numbers) {return numbers.map((num, index, arr) => {const currentSum = arr.slice(0, index + 1).reduce((acc, val) => acc + val, 0);return { value: num, runningTotal: currentSum };});
}const numbers = [1, 2, 3, 4, 5];
const result = getRunningTotal(numbers);
console.log(result); // 输出: [{ value: 1, runningTotal: 1 }, { value: 2, runningTotal: 3 }, ...]
以上代码展示了如何使用闭包来计算数组中每个元素到当前位置的累计和,闭包在 map
函数的回调中捕获并维护了当前的累计值。
这些代码示例展示了闭包在不同场景下的应用,包括模块化、变量生命周期管理、异步操作、事件处理以及状态跟踪等。闭包的核心价值在于它能捕获并保持对外部作用域的访问,提供了一种灵活且安全的方式来组织和控制代码中的数据访问。