什么是闭包
在函数被调用时会为这个函数创建一个执行上下文,该上下文中有一个活动对象,对象中包含函数中的变量;并创建一个作用域链,该作用域链的第一个指针指向该函数的活动对象,之后的指针指向外层函数活动对象,以此,直到指向全局对象的变量对象。当函数执行结束时,该函数的活动对象会被销毁,但是对于闭包是不一样的。
闭包是指哪些引用另一个函数作用域中变量的函数,通常闭包通过嵌套函数实现。具体解释起来就是嵌套函数中,外部函数返回一个内部函数,但是由于内部函数引用了外部函数的变量,所以导致即使外部函数执行结束后,依然在内存中形成一个供内部函数使用的环境包。
闭包例子1:
// 彻底分析下代码
// t1函数的声明,返回一个匿名函数
function t1() {
let count = 10
return function () {
console.log(count);
}
}
// t1函数执行结束,t1函数的作用域链被销毁。
// 但是t1内部有个匿名函数,该匿名函数同样存在作用域链。
// 该作用域链的第一个对象是自己的活动对象(活动对象在词法分析阶段创建),
// 作用域链上的第二个对象是t1的活动对象, 第三个对象是全局变量对象。
// 匿名函数内存地址赋值给tmp,还未执行
let tmp = t1()
// 声明操作,在全局变量对象上会创建这个变量
let count = 30
// 匿名函数执行,打印count,所以通过作用域链找count
// 首先找自己的活动对象,没有
// 然后找作用域链的第二个对象,即t1的活动对象,找到count,则直接打印count为10
tmp() // 10
函数的作用域取决于声明时而非调用时
闭包例子2:
for (var i = 0; i < 5; i++) {
setTimeout(() => {
console.log(i) // 5 5 5 5 5
}, 1000 * i);
}
为什么会都打印5?首先setTimeout的作用是在一定的时间后将回调函数加入事件队列。可以知道回调函数最少需要一秒才会执行,但是for循环是在以瞬间执行完的。而i其实是个全局变量。所以回调函数执行的时候 i 已经变成5了,所以会打印5个5。
for (var i = 0; i < 5; i++) {
(function (j) {
setTimeout(() => {
console.log(j) // 0 1 2 3 4
}, 1000 * j);
})(i)
}
为什么又会这样打印?其实这里正是因为闭包,最外层是一个立即执行的函数,并传入参数。形参j接收实参并形成一个私有作用域上,供对应的回调函数使用。
闭包的应用
闭包的第一个用途是使我们在函数外部能够访问到函数内部的变量,例如模块化的实现
另一个用途是使已经运行结束的函数上下文中的变量对象继续留在内存中,实现变量缓存,例如全局计时器
function module() {
const list = [];
function append(val) {
return list.push(val)
}
function getOne(index) {
return list[index]
}
return {
append,
getOne
}
}
const m = module()
m.append(3)
m.append(5)
m.getOne(1) // 5