闭包是指有权访问另一个函数作用域中变量的函数。闭包的形成需要满足两个条件:
function outer() {
var outerVar = 'outer';
function inner() {
console.log(outerVar); // 访问外部函数的变量
}
return inner;
}
const closure = outer();
closure(); // 'outer'
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
console.log(counter.getCount()); // 2
// count 无法直接访问,实现了封装
function createTimer() {
let startTime = Date.now();
return function() {
return Date.now() - startTime;
};
}
const timer = createTimer();
setTimeout(() => {
console.log(timer()); // 输出经过的时间
}, 1000);
const module = (function() {
let privateVar = 0;
return {
publicMethod: function() {
privateVar++;
return privateVar;
}
};
})();
module.publicMethod(); // 1
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)); // 15
function setupButton(buttonId, message) {
const button = document.getElementById(buttonId);
button.addEventListener('click', function() {
alert(message); // 闭包保存了 message
});
}
setupButton('btn1', 'Hello');
setupButton('btn2', 'World');
经典问题:循环中使用闭包
// 问题代码
for (var i = 0; i < 3; i++) {
setTimeout(function() {
console.log(i); // 输出 3 次 3
}, 100);
}
// 解决方案1:使用 IIFE
for (var i = 0; i < 3; i++) {
(function(j) {
setTimeout(function() {
console.log(j); // 0, 1, 2
}, 100);
})(i);
}
// 解决方案2:使用 let
for (let i = 0; i < 3; i++) {
setTimeout(function() {
console.log(i); // 0, 1, 2
}, 100);
}
// 解决方案3:使用 bind
for (var i = 0; i < 3; i++) {
setTimeout(function(j) {
console.log(j);
}.bind(null, i), 100);
}
闭包可能导致内存泄漏,需要注意:
// 可能导致内存泄漏
function attachHandler() {
const element = document.getElementById('button');
element.onclick = function() {
// 闭包引用了 element,即使元素被移除,内存也不会释放
element.style.backgroundColor = 'red';
};
}
// 改进:避免不必要的引用
function attachHandler() {
const element = document.getElementById('button');
const id = element.id;
element.onclick = function() {
// 只引用需要的属性
document.getElementById(id).style.backgroundColor = 'red';
};
}
// 使用完毕后解除引用
let closure = createClosure();
closure(); // 使用
closure = null; // 解除引用,帮助垃圾回收