闭包指的是那些引用了另一个函数作用域中变量的函数,通常是在嵌套函数中实现的。
举个栗子,createCounter 接受一个参数 n,然后返回一个匿名函数,这个匿名函数是闭包,它可以访问外部函数 createCounter 的局部变量 n。因为这个内部函数在外部有被引用,该函数会不会被销毁,n的值也会被保存。
function createCounter(n) {
return function () {
return n++
};
};
const a = createCounter(1)
a()//1
a()//2
function createCounter2() {
let n = 1
return function increment() {
console.log(++n)
};
};
const b = createCounter2()
b()//2
b()//3
return返回的函数那个函数外部有引用,才会被保存;而show函数每次被调用都会重新被加载。
function createCounter3() {
return function () {
let n = 1
function show(){
console.log(++n)
}
show()
};
};
const c = createCounter3()
c()//2
c()//2
function createCounter4() {
return function () {
let n = 1
return function show(){
console.log(++n)
}
};
};
const d = createCounter4()()//show函数外部有引用
d()//2
d()//3
理解作用域链的创建和使用,对理解闭包非常重要。
在调用一个函数时,会为这个函数调用创建一个执行上下文,并创建一个作用域链。
function compare(value1, value2) {
if (value1 < value2) {
return -1;
} else if (value1 > value2) {
return 1;
} else {
return 0;
}
}
let result = compare(5, 10);
这里定义的 compare()函数是在全局上下文中调用的。
在定义函数时,就会为它创建作用域链,预装载全局变量对象,并保存在内部的[[Scope]]中。在调用这个函数时,会创建相应的执行上下文,然后通过复制函数的[[Scope]]来创建其作用域链。
第一次调用 compare()时,会为它创建一个包含 arguments、value1 和 value2 的活动对象,这个对象是其作用域链上的第一个对象。什么是arguments对象?
compare()作用域链上的第二个对象是全局上下文的变量对象,其中包含 this、result 和 compare。
全局上下文中的叫变量对象,它会在代码执行期间始终存在。比如在浏览器中是 window
对象。
函数局部上下文中的叫活动对象,只在函数执行期间存在。当函数执行完毕后,活动对象会被销毁。
函数内部代码在访问变量时,会从作用域链中查找变量。函数执行完毕后,局部活动对象会被销毁,内存中就只剩下全局作用域。不过,闭包就不一样了。
在一个函数内部定义的函数会把其包含函数的活动对象添加到自己的作用域链中。
因此,在createCounter()函数中,匿名函数的作用域链中实际上包含createCounter的活动对象(也就是arguments和它的形参),所以在内部的函数可以访问到外部的参数。因为匿名函数中有对n的引用,所以执行完毕后createCounter不会被销毁。
function createCounter(n) {
return function () {
return n++
};
};
//1.创建函数
const a = createCounter(1)
//2.调用函数
a()//1
a()//2
//3.除对函数的引用,这样就可以释放内存了
a = null
创建的createCounter 函数被保存在变量 a 中。把 a设置为等于 null 会解除对函数的引用,从而让垃圾回收程序可以将内存释放掉,作用域链也会被销毁。
闭包允许创建私有变量,这对于封装和隐藏实现细节非常有用。通过在函数内部定义变量,并返回一个访问这个变量的函数,可以创建一个私有作用域。
function createCounter() {
let count = 0;
return function() {
count++;
return count;
};
}
const counter = createCounter();
console.log(counter()); // 输出 1
console.log(counter()); // 输出 2
闭包可用于实现模块化开发,创建私有作用域,防止变量污染全局命名空间。
const module = (function() {
let privateVariable = 'I am private';
return {
getPrivateVariable: function() {
return privateVariable;
},
setPrivateVariable: function(value) {
privateVariable = value;
}
};
})();
console.log(module.getPrivateVariable()); // 输出 'I am private'
module.setPrivateVariable('Updated value');
console.log(module.getPrivateVariable()); // 输出 'Updated value'
在事件处理程序中,闭包可以用来维持对外部作用域的引用,以便在事件触发时访问外部变量。
function setupEventListener() {
let count = 0;
document.getElementById('myButton').addEventListener('click', function() {
count++;
console.log(`Button clicked ${count} times`);
});
}
setupEventListener();
在定时器中使用闭包,可以保存定时器内的变量状态,而不受外部环境的影响。
function startTimer() {
let seconds = 0;
const timer = setInterval(function() {
seconds++;
console.log(`Timer: ${seconds} seconds`);
}, 1000);
return function stopTimer() {
clearInterval(timer);
};
}
const stopTimer = startTimer();
// ...一些代码后
stopTimer(); // 停止定时器
在循环中使用闭包,可以保持对每次迭代的独立作用域,防止变量共享问题。
for (let i = 1; i <= 5; i++) {
setTimeout(function() {
console.log(`Delayed log: ${i}`);
}, i * 1000);
}
可以实现对函数调用结果的缓存,提高性能。
function memoize(fn) {
const cache = {};
return function(...args) {
const key = JSON.stringify(args);
if (cache[key]) {
console.log('Result retrieved from cache');
return cache[key];
} else {
const result = fn(...args);
cache[key] = result;
return result;
}
};
}
const memoizedAdd = memoize(function(x, y) {
console.log('Performing expensive calculation');
return x + y;
});
console.log(memoizedAdd(2, 3)); // 输出 'Performing expensive calculation' 和 5
console.log(memoizedAdd(2, 3)); // 输出 'Result retrieved from cache' 和 5