闭包是JavaScript中一个非常核心的概念,很多开发者刚接触时都会觉得难以理解,甚至在实际写代码时已经用到了闭包却完全没有意识到。接下来我们会从基础概念开始,一步步拆解闭包的相关知识。

闭包的基本定义
在JavaScript中,闭包指的是有权访问另一个函数作用域中变量的函数,简单来说,就是当内部函数被保存到外部时,就会生成闭包。闭包的本质是内部函数可以记住并访问它的词法作用域,即使这个函数在它的词法作用域之外执行。
要理解闭包,首先需要知道JavaScript的作用域规则:JavaScript采用的是词法作用域,也就是函数的作用域在函数定义的时候就已经确定了,和函数调用的位置没有关系。
闭包的形成条件
闭包的形成需要满足三个基本条件:
- 存在嵌套的函数结构,也就是一个函数内部定义了另一个函数
- 内部函数引用了外部函数的变量或者参数
- 内部函数被外部函数返回,或者在外部函数的外部被调用
闭包的基础示例
我们来看一个最简单的闭包示例,帮助理解闭包的工作方式:
// 外部函数
function outer() {
// 外部函数的局部变量
let count = 0;
// 内部函数,引用了外部函数的count变量
function inner() {
count++;
console.log(count);
}
// 返回内部函数,此时inner被保存到外部,形成闭包
return inner;
}
// 调用outer得到inner函数
const counter = outer();
// 调用counter,此时inner在outer的作用域之外执行,依然可以访问count
counter(); // 输出1
counter(); // 输出2
counter(); // 输出3上面的代码中,inner函数引用了outer函数的count变量,并且outer函数返回了inner函数。当我们调用outer得到counter之后,outer函数的执行上下文已经出栈,但是count变量并没有被销毁,因为inner函数形成的闭包还在引用它,每次调用counter都会修改这个count的值。
闭包的常见应用场景
1. 实现数据私有化
闭包可以让变量不被外部直接访问和修改,只能通过特定的方法操作,实现类似面向对象中私有属性的效果:
function createPerson() {
// 私有变量,外部无法直接访问
let name = '张三';
return {
// 公开方法,用于获取私有变量
getName: function() {
return name;
},
// 公开方法,用于修改私有变量
setName: function(newName) {
name = newName;
}
};
}
const person = createPerson();
console.log(person.getName()); // 输出张三
person.setName('李四');
console.log(person.getName()); // 输出李四
// 直接访问name会返回undefined,因为name是私有变量
console.log(person.name); // 输出undefined2. 函数柯里化
函数柯里化是把接受多个参数的函数转换成接受一个单一参数,并且返回接受余下的参数而且返回结果的新函数的技术,闭包是实现柯里化的核心:
// 普通的多参数求和函数
function sum(a, b, c) {
return a + b + c;
}
// 柯里化后的函数
function currySum(a) {
return function(b) {
return function(c) {
return a + b + c;
};
};
}
// 调用方式
console.log(currySum(1)(2)(3)); // 输出6
// 也可以分步调用
const addOne = currySum(1);
const addOneAndTwo = addOne(2);
console.log(addOneAndTwo(3)); // 输出63. 防抖和节流
防抖和节流是前端开发中常用的性能优化手段,它们的实现也依赖闭包来保存定时器或者上次执行的时间等状态:
// 防抖函数,连续触发事件时,只在最后一次触发后等待wait时间执行
function debounce(fn, wait) {
let timer = null; // 用闭包保存定时器变量
return function(...args) {
// 每次触发先清除之前的定时器
if (timer) clearTimeout(timer);
// 重新设置定时器
timer = setTimeout(() => {
fn.apply(this, args);
}, wait);
};
}
// 节流函数,连续触发事件时,每隔一段时间只执行一次
function throttle(fn, interval) {
let lastTime = 0; // 用闭包保存上次执行的时间
return function(...args) {
const now = Date.now();
// 如果距离上次执行的时间大于等于间隔时间,就执行函数
if (now - lastTime >= interval) {
lastTime = now;
fn.apply(this, args);
}
};
}闭包的注意事项
虽然闭包非常有用,但是使用的时候也需要注意一些问题:
- 闭包会持有外部函数的作用域引用,导致这些作用域中的变量无法被垃圾回收,如果滥用闭包可能会造成内存泄漏,不需要的时候要及时解除对闭包的引用
- 在循环中使用闭包的时候要特别注意作用域的问题,比如经典的循环绑定事件问题,需要正确使用闭包或者let块级作用域来解决
总的来说,闭包是JavaScript中非常强大的特性,理解闭包的原理和使用场景,能帮助我们写出更灵活、更优雅的代码,也能更好地理解很多JavaScript库和框架的实现逻辑。
JavaScript闭包作用域函数变量修改时间:2026-05-29 23:41:38