一.闭包的定义
闭包是指函数和其周围的词法环境的引用的组合。
简单来说,就是函数可以记住并访问其在定义时的作用域内的变量,即使该函数在其它作用域调用。
也就是说,闭包让你可以在一个内层函数中访问到其外层函数的作用域。
function createCounter() {
let count = 0; // 私有变量
return {
increment() {
return ++count;
},
getCount() {
return count;
}
};
}
const counter = createCounter();
console.log(counter.increment()); // 1
console.log(counter.increment()); // 2
console.log(counter.getCount()); // 2
- 函数携带着它定义时的作用域信息
二.闭包的原理(底层机制)
在 JS 中,每次函数执行都会创建一个执行上下文栈(Execution Context),上下文包含变量环境(Variable Environment)。当内部函数被返回或传出外部时,JavaScript 引擎会将相关变量的引用保留在内存中,不会立即销毁,从而形成闭包。
三.闭包的应用场景
①:数据封装 / 私有变量
function counter() {
let count = 0;
return {
increment: () => ++count,
decrement: () => --count,
get: () => count
};
}
let c = counter();
console.log(c.increment()); // 1
console.log(c.get()); // 1
②:函数工厂
function multiply(x) {
return function(y) {
return x * y;
};
}
const multiplyByTwo = multiply(2);
console.log(multiplyByTwo(4)); // 8
③:事件处理
<template>
<button id="btn1">按钮1</button>
<button id="btn2">按钮2</button>
</template>
<script setup>
import { onMounted } from 'vue';
function handleClick(message) {
return function() {
console.log(message);
};
}
//等待组件挂载完
onMounted(() => {
document.getElementById("btn1")?.addEventListener('click', handleClick('按钮1被点击了'));
document.getElementById("btn2")?.addEventListener('click', handleClick('按钮2被点击了'));
});
</script>
④:模块化实现
使用闭包创建私有变量和方法,形成模块化代码结构
const module = (function() {
let privateVar = 'private';
function privateMethod() {
return privateVar;
}
return {
publicMethod() {
return privateMethod();
}
};
})();
⑤:循环中的闭包
// 错误示例
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 0); // 3,3,3
}
// 正确示例
// 使用let和const替代var
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 0); // 0,1,2
}
// 正确示例
// 使用闭包
for (var i = 0; i < 3; i++) {
(function(j) {
setTimeout(function() {
console.log(j); // 0 1 2
}, 100);
})(i);
}
四.闭包的缺点
-
容易造成内存泄漏(引用的变量得不到释放)。
-
调试难度增大(变量状态隐藏在函数中)。
注意内存管理
// 内存泄漏示例
function leakMemory() {
const largeData = new Array(1000000);
return function() {
console.log(largeData[0]);
}
}
// 避免内存泄漏
function avoidLeak() {
const data = process(largeData);
return function() {
console.log(data); // 只保留需要的数据
}
}
五、闭包与垃圾回收
通常,函数执行完后其作用域就会被销毁。但闭包中被引用的变量会被保留在内存中,只要闭包仍在使用,变量就不会被回收。
这可能造成内存问题,尤其是在网页中绑定大量 DOM 事件但未解绑的情况下。