闭包这词估计已经听烂了,这里随便再复习下它的概念。
闭包 (Closure):函数和其周围的状态(词法环境)的引用捆绑在一起形成闭包。
特点1:可以在另一个作用域中调用一个函数的内部函数并访问到该函数的作用域中的成员。
上文提到的,函数作为返回值和那个支付的例子,就是闭包:
function makeFn() {
let msg = 'Hello function'
return function () {
console.log(msg)
}
}
const fn = makeFn()
fn()
上面代码中,fn 是 makeFn 返回的另一个函数,fn 在 makeFn 的外部调用,也能取到 makeFn 内部的局部变量。
上文中模拟的支付案例:
// once
function once(fn) {
let done = false
return function () {
if (!done) {
done = true
return fn.apply(this, arguments)
}
}
}
let pay = once(function (money) {
console.log(`支付:${money} RMB`)
})
// 只会支付一次
pay(5)
pay(5)
pay(5)
上面代码中的 done 起到一个拦截的作用,done 是 once 的一个局部变量,在 once 外部无法更改它,保证了安全性。
pay 是 once 返回的一个新函数,pay 在 once 外部调用能取到 done ,从能正确的执行我们想要的逻辑。
闭包的本质:函数在执行的时候会放到一个执行栈上,当函数执行完毕之后会从执行栈上移除,但是堆上的作用域成员因为被外部引用不能释放,因此内部函数依然可以访问外部函数的成员。
下面,通过调试,来看看来看看闭包的执行过程中,发生了什么事情:
用来供观察的代码:
// 生成计算数字的多少次幂的函数
function makePower(power) {
return function (x) {
return Math.pow(x, power)
}
}
let power2 = makePower(2)
let power3 = makePower(3)
console.log(power2(4))
console.log(power2(5))
console.log(power3(4))
代码写好后,打开浏览器控制台,点源代码,选择刚才所编写的文件,在power2,即首次产生闭包的地方打上一个断点,如下:
刷新浏览器,此时程序会停在pow2处,此时按F11进入这个函数,再看右边相关信息:
可以看到makePower函数已经进入了调用栈,在下边的作用域中,产生了一个局部作用域,这个局部作用域中有个power为2的参数,并且其中的this指向window。
接着往下执行,当执行到power3的时候,可以看到,右边调用栈里已经没有了makePower,表示此时这个函数已经弹出调用栈,验证了“函数执行完,即退出调用栈的”的说法,相关更多知识会在后边的同异步编程中提到
继续往下执行,进入power3函数内部,此时右边相关信息如下:
可看到,makePower再次进入调用栈,并再产生了一个局部作用域,这个作用域中power为3,this指向window。
接着往下执行,当程序进入求平方阶段时,可以看到右边这样的信息:
可以看到,尽管makePower已经退出了调用栈,但是曾经传入的“2”,在这里以闭包的形式存储下来了。
PS:看红框,那个就是闭包,但是这坑爹的翻译给翻译成“关闭”了,所以说,汉化有风险,我准备退回英文版了,ennn…
在调试里,能清晰的看清楚执行过程等很多东西,建议多几种花样试试,能开开眼界。
经过上面的执行过程调试,我们在调试信息里便清晰的证明了闭包的本质:
函数在执行的时候会放到一个执行栈上,当函数执行完毕之后会从执行栈上移除,但是堆上的作用域成员因为被外部引用不能释放,因此内部函数依然可以访问外部函数的成员。
现在,对闭包了解多少了呢?
文章内容输出来源:拉勾大前端高薪训练营,以上文章中的内容根据老师讲课的语音和代码,结合自己的理解编辑完成。