闭包
闭包(closure)是指:形成了闭包的函数+及其周围被引用的存在 打包在一起
闭包 = 形成闭包的函数 + 被引用的(裁剪过的)作用域
其中,函数形成闭包的条件是:它引用了更大的作用域中的变量
(比如嵌套函数:子函数引用了亲函数中的变量)
也就是说:
//这里形成的闭包包括:
function outerFunction() {
// 包括outerFunction的(被裁剪的)作用域
let outerVariable: number = 10; // 包括我,因为我被引用了
let outerVarAnother: number = 20; // 不包括我,因为我没被引用,被裁剪掉了
function innerFunction(): void {// 包括我,我就是那个形成闭包的函数
// 内部函数引用外部函数的变量,形成闭包
console.log("外部变量的值 = " + outerVariable);
}
return innerFunction; // 返回内部函数
}
闭包通常是在嵌套函数中实现的(但并不是只有返回一个小函数的大函数能造成闭包!)
闭包的意义
好处:
闭包允许通过内部函数访问外部函数的作用域
这种特性让异步回调成为可能的
坏处:
闭包内的变量不会自动销毁,会被“一直”保存在内存中
闭包的内存保存机制
js的内存管理是基于作用域链的。垃圾回收器定期遍历作用域链,不在链中的变量被视为垃圾,进行删除。
在普通(无闭包)的情况下,在函数执行完毕后:
局部活动对象(包含所有挂在这个活动对象下的变量,也就是函数内的变量)被销毁
内存中只剩下全局作用域(包含所有挂在变量对象下的变量,也就是全局变量)
但在有闭包的情况下:
嵌套函数的情况:
以嵌套函数为例,代码见下:
如果没有手动 closureExample = null;,那么在closureExample还存在的时间内
- innerFunc一直被引用
- number一直被innerFunc引用
- 因此,outerFunc(裁剪后的作用域)会一直占用内存。
在这种情况下,outerFunc占用内存的时间 = closureExample占用内存的时间,而不是在outerFunction();执行完毕后就可以被垃圾回收。
function outerFunction() {
let outerVariable: number = 10; // 外部函数的变量
function innerFunction(): void {
console.log("外部变量的值 = " + outerVariable);
}
return innerFunction; // 返回内部函数
}
let closureExample: (() => void) | null = outerFunction(); // 调用外部函数,获取内部函数
closureExample(); // 调用内部函数
closureExample = null;//垃圾回收number
/*
假设上下文中只有let closureExample: (() => void) | null = outerFunction();这一个对innerFunc的引用,那么:
closureExample = null;
1 - 手动删除对innerFunc的引用
2 - innerFunc的变量对象(outerFunc.number的引用)被垃圾回收
3 - outerFunc的变量对象(包括outerFunc.number)引用清零,被垃圾回收
*/
异步回调的情况:
以异步回调为例,代码见下:
- 在setTimeout执行之前,data会一直被保存在内存中
- 在setTimeout执行callback(data)之后,(假设上下文中没有其他对这个闭包的引用),data才能被垃圾回收。
注意,这里不需要像嵌套函数一样手动删除引用,因为这里的闭包函数并没有(通过return)暴露给外界去引用。setTimeout执行完毕,它的变量对象直接挂掉,data也就能被自动删除了。
function fetchData(callback:(result: string)=>any) {
let data = "这是外部函数的数据"; // 外部函数的变量
setTimeout(function() {
callback(data); // 在回调函数中访问外部函数的变量,形成闭包
}, 1000);
}
fetchData(function(result) {
console.log(`回调函数中获取到的数据为: ${result}`);
});