在工作中很多时候会用到闭包,但是对于闭包的原理还比较懵懂,索性写一篇文章来聊聊闭包
什么是闭包
闭包在《js高程》中的解释是:「可以访问外部作用域的函数」,即,只要某函数定义在另一个函数内,并,它就是闭包,无论它是否使用了外部函数作用域中的变量。
举个例子
function fn() {
let a = '1';
let b = '2';
return function() {
console.log(a)
}
}
let c = fn;
c()
以上代码,匿名函数fn返回的匿名函数可以访问外部作用域的变量a,因此fn是一个闭包
闭包有什么作用
当闭包函数被全局变量引用时,由于闭包函数持有作用域外的变量,使该变量不会被浏览器的垃圾回收机制清除
为什么闭包可以做到使变量不被清除?
如果闭包函数被外部变量引用 (该点很关键),那么这个存在于堆内存中的闭包函数对象就一直存在着(因为它被外部引用着,只要外部作用域不退出,就不会被垃圾回收清除)。
那么这个函数对象上的 [[Scope]]属性(即作用域链)自然就不会被清除,那么作用域链引用的所有层级包含函数的活动对象就不会被清除,而函数使用的外部变量都在该属性上,因此也不会被清除。
准确说是闭包所包含的整个作用域链所引用的 变量对象 中的值不会被清除。
这也就意味着,所有返回函数的函数被接收之后,都有占用内存的闭包问题。
我们同时要关注的是,无论闭包的定义如何(是内部函数,还是被引用的外部函数),按照语言解释器的机制,无法被垃圾清除的是函数创建时的作用域链。
顺便谈谈词法作用域
-
词法作用域即是静态作用域名,其与动态作用域形成对比,
-
静态作用域: 函数执行时是使用的是函数定义时候的变量作用域;
-
动态作用域: 函数执行的时候使用的是函数执行时的变量作用域
看一个有趣的例子
var name = 'Mike'; //第一次定义name
function showName() {
console.log(name); //输出 Mike 还是 Jay ?
}
function changeName() {
var name = 'Jay';
showName()
}
changeName(); // 最后结果是输出 Mick
最后结果是输出Mick, 原因是由于,在执行showName时,使用的是定义时的变量作用域,基于作用域链,因此直接找到window对象上的name,即 Mick