Js
闭包(Closure)
什么是闭包?闭包的应用场景和注意事项?
核心答案
**闭包(Closure)**是指函数能够访问其外部(词法)作用域中的变量,即使外部函数已经执行完毕。闭包是 JavaScript 的一个重要特性,它让函数"记住"并访问创建时的环境。
闭包的定义
闭包的形成需要满足三个条件:
- 嵌套函数:内部函数定义在外部函数内部
- 内部函数引用外部变量:内部函数使用了外部函数的变量
- 内部函数被返回或传递:内部函数在外部函数执行完后仍可被调用
闭包的基本示例
function outerFunction(x) {
// 外部函数的变量
const outerVariable = x;
// 内部函数(闭包)
function innerFunction(y) {
// 内部函数访问外部函数的变量
console.log(outerVariable + y);
}
// 返回内部函数
return innerFunction;
}
const closure = outerFunction(10);
closure(5); // 输出:15
// outerFunction 执行完毕,但 innerFunction 仍能访问 outerVariable闭包的工作原理
- 词法作用域:JavaScript 使用词法作用域,函数在定义时就确定了作用域链
- 作用域链:内部函数通过作用域链访问外部函数的变量
- 变量持久化:外部函数执行完毕后,其变量被内部函数引用,不会被垃圾回收
经典闭包问题:循环中的闭包
// 问题代码:输出都是 5
for (var i = 0; i < 5; i++) {
setTimeout(function() {
console.log(i); // 输出:5, 5, 5, 5, 5
}, 100);
}
// 解决方案1:使用 IIFE(立即执行函数)
for (var i = 0; i < 5; i++) {
(function(j) {
setTimeout(function() {
console.log(j); // 输出:0, 1, 2, 3, 4
}, 100);
})(i);
}
// 解决方案2:使用 let(块级作用域)
for (let i = 0; i < 5; i++) {
setTimeout(function() {
console.log(i); // 输出:0, 1, 2, 3, 4
}, 100);
}
// 解决方案3:使用 bind
for (var i = 0; i < 5; i++) {
setTimeout(function(j) {
console.log(j);
}.bind(null, i), 100);
}延伸追问
1. 闭包的应用场景有哪些?
回答:常见应用场景:
1. 数据私有化(模块模式)
function createCounter() {
let count = 0; // 私有变量
return {
increment: function() {
count++;
return count;
},
decrement: function() {
count--;
return count;
},
getCount: function() {
return count;
}
};
}
const counter = createCounter();
console.log(counter.increment()); // 1
console.log(counter.increment()); // 2
// count 无法直接访问,实现了数据封装2. 函数工厂
function createMultiplier(multiplier) {
return function(number) {
return number * multiplier;
};
}
const double = createMultiplier(2);
const triple = createMultiplier(3);
console.log(double(5)); // 10
console.log(triple(5)); // 153. 防抖和节流
// 防抖
function debounce(func, delay) {
let timeoutId;
return function(...args) {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => func.apply(this, args), delay);
};
}
// 节流
function throttle(func, limit) {
let inThrottle;
return function(...args) {
if (!inThrottle) {
func.apply(this, args);
inThrottle = true;
setTimeout(() => inThrottle = false, limit);
}
};
}4. 回调函数
function fetchData(url, callback) {
// 模拟异步请求
setTimeout(() => {
const data = { url, status: 'success' };
callback(data); // 回调函数形成闭包,可以访问外部变量
}, 1000);
}
fetchData('/api/data', function(data) {
console.log('Received:', data);
});5. 事件处理
function setupButtons() {
const buttons = document.querySelectorAll('button');
buttons.forEach(function(button, index) {
button.addEventListener('click', function() {
console.log('Button', index, 'clicked');
// 闭包让每个按钮都能访问自己的 index
});
});
}2. 闭包可能导致的问题?
回答:主要问题:
1. 内存泄漏
// 问题:闭包持有大对象的引用,导致无法释放
function createHandler() {
const largeData = new Array(1000000).fill('data');
return function() {
// 即使不使用 largeData,闭包也会持有引用
console.log('Handler called');
};
}
// 解决:使用完后手动清理
function createHandler() {
const largeData = new Array(1000000).fill('data');
return function() {
console.log('Handler called');
// 使用完后设置为 null
largeData = null;
};
}2. 性能问题
- 闭包会延长变量的生命周期,占用内存
- 每次创建闭包都会创建新的作用域链,有一定性能开销
3. 意外的变量共享
// 问题:所有闭包共享同一个变量
function createFunctions() {
const result = [];
for (var i = 0; i < 3; i++) {
result[i] = function() {
return i; // 所有函数都返回 3
};
}
return result;
}
// 解决:使用 IIFE 或 let
function createFunctions() {
const result = [];
for (let i = 0; i < 3; i++) {
result[i] = function() {
return i; // 每个函数返回不同的值
};
}
return result;
}3. 如何判断是否存在闭包?
回答:判断方法:
- 查看作用域链:在浏览器 DevTools 中查看函数的
[[Scopes]]属性 - 检查变量访问:函数访问了外部作用域的变量
- 函数被返回或传递:内部函数在外部函数执行完后仍可调用
示例:
function outer() {
const x = 10;
function inner() {
console.log(x); // 访问外部变量,形成闭包
}
return inner;
}
const fn = outer();
// 在 DevTools 中查看 fn 的 [[Scopes]],可以看到 Closure (outer)4. 闭包和 this 的关系?
回答:需要注意 this 的指向:
const obj = {
name: 'Object',
getName: function() {
return function() {
return this.name; // this 指向全局对象,不是 obj
};
}
};
console.log(obj.getName()()); // undefined(严格模式下)或 ''(非严格模式)
// 解决方案1:使用箭头函数
const obj = {
name: 'Object',
getName: function() {
return () => {
return this.name; // this 指向 obj
};
}
};
// 解决方案2:保存 this
const obj = {
name: 'Object',
getName: function() {
const self = this;
return function() {
return self.name;
};
}
};
// 解决方案3:使用 bind
const obj = {
name: 'Object',
getName: function() {
return function() {
return this.name;
}.bind(this);
}
};(注:文档部分内容可能由 AI 生成)