闭包是能访问到外部函数作用域中变量的函数
闭包代码示例:
function outer() {
let num = 10
//内部函数要作为返回值返回
function init() {
num++ //内部函数引用外部函数outer中的num变量
console.log('num', num);
}
return init
}
let result = outer() // 调用 outer 函数,返回一个闭包
result() // 输出 11
result() // 输出 12
变量的可见性是由它在代码中被声明的位置决定的,即在函数创建时就已经确定了,和调用无关,闭包利用的就是词法作用域
闭包的生命周期是在其创建时开始,并在不再被引用时结束。
闭包的生命周期取决于是否还有对闭包的引用。只要闭包仍然被其他代码、变量或函数引用,它就会一直存在。当没有任何引用指向闭包时,垃圾回收机制将自动回收闭包所占用的内存空间。
如下代码示例,将result设置为null,便解除对闭包的引用
function outer() {
let num = 10
//内部函数要作为返回值返回
function init() {
num++ //内部函数引用外部函数outer中的num变量
console.log('num', num);
}
return init
}
let result = outer() // 调用 outer 函数,返回一个闭包
result() // 输出 11,每调用一次num+1
// 此时,闭包仍然被变量 result 引用,因此它的生命周期还未结束
result = null; // ⭐⭐解除对闭包的引用
场景1:封装私有变量
场景2:延迟执行
场景3:模块化
场景4:缓存数据
闭包可以创建私有变量,外部无法直接访问或修改,从而保护变量不受外部的干扰。
function createCounter() {
let count = 0
function add() {
count++
console.log(count);
}
return add
}
const counter = createCounter()
counter() //打印 1
counter() //打印 2
counter() //打印 3
闭包可以在异步操作中用于保存状态或数据,并在需要时执行回调函数。
function delays(delay) {
return function () {
setTimeout(() => {
console.log(`延迟${delay}`);
}, delay)
}
}
const delayFunction = delays(2000)
delayFunction()
利用闭包可以创建模块化的代码结构,将相关的函数和数据封装在一个闭包内部,通过返回的公共接口来访问和操作这些内部成员。这种方式可以避免全局命名空间的污染,提高代码的可维护性和重用性。
const myModule = (function () {
let count = 0
function add(num) {
count += num
}
function dec(num) {
count -= num
}
function getCount() {
return count
}
return { add, dec, getCount }
})();
myModule.add(2)
myModule.dec(1)
console.log(myModule.getCount()); //输出1
使用闭包可以在函数执行完成后,仍然保持对其词法环境中变量的访问权限。这样可以实现缓存值,避免重复计算或重复访问外部数据。在这个例子中,getName
函数记住了 age
的值,可以在后续调用中使用同一个值而不需要重新计算或获取。
function bar() {
let name = 'pig'
let age = 1
let innerBar = {
getName() {
console.log(age);
return name
},
setName(newName) {
name = newName
}
}
return innerBar
}
const foo = bar()
console.log(foo.getName()); // 输出 1 pig
foo.setName('dog')
console.log(foo.getName()); // 输出 1 dog
使用闭包时,变量会一直存在于内存中,可能造成内存占用过高。如果闭包被滥用,可能导致内存泄漏问题。
由于闭包需要记住引用环境,所以在内存中的查找会比较耗时,可能会影响函数的执行效率。