浅浅认识一下js中的闭包
闭包
- 根据js词法作用域的规则,内部函数总是能访问外部函数中的变量,当通过调用一个外部函数返回的一个内部函数后,即使外部函数执行已经结束了,但是内部函数引用了外部函数中的变量也就需要被保存在内存中,我们把这些变量的集合叫做闭包。
词法作用域
- 在词法作用域中,变量的作用域由它们在源代码中出现的位置决定,而不是由它们在运行时被引用的位置决定。即一个域所处的环境,是由函数声明的位置来决定的。
闭包的原理
作用域链
- js引擎在查找变量的时候回先在函数中查找,找不到就会根据outer的指向去到外层作用域中查找,层层往上,这种查找的关系链就称为作用域链 。
- 函数在执行前预编译,会创建执行上下文对象。
- 变量环境中有一个内定的outer属性用于指明该函数的外层作用域是谁。
- outer的指向是根据词法作用域来定的。
变量对象
- 函数的执行上下文(Execution Context)中包含一个变量对象,它存储了函数的作用域内的所有变量和函数声明。当一个函数形成闭包时,它会保留对其外部执行上下文中的变量对象的引用。
垃圾回收
- 通常,当一个函数执行完毕,其执行上下文会被销毁(出栈),变量对象也随之消失。但是,如果一个内部函数形成了闭包,那么外部函数的执行上下文将不会被垃圾回收,因为内部函数仍然引用着它的变量对象。
闭包的形成
- 闭包通常在函数内部定义函数时形成。当内部函数被返回或以某种方式保存下来供以后使用时,它就形成了一个闭包,因为它保持了对其外部作用域中变量的引用,即使外部函数已经完成了执行。
例如:
function foo(){
var name = 'abc';
function bar(){
console.log(count,age);
}
var count = 1;
var age = 18;
return bar;
}
var age = 20;
const baz = foo();
baz();
bar函数是一个闭包,此时闭包内的变量分别为count=1和age=18。即使foo函数已经执行完毕,bar依然能访问到foo函数作用域中的变量。
当bar函数内部试图访问count和age时,它首先在其直接的作用域中查找,由于bar是在foo的作用域中定义的,所以它能找到count和age,并且它们的值分别是1和18。尽管全局作用域中也有一个age变量,但bar函数不会访问到它,因为它首先找到了foo作用域中的age。
因此,当执行baz() 时,控制台上将输出1和18,这是因为bar函数内部的console.log语句打印出了foo作用域中count和age的值。
闭包的作用
优点
-
封装私有变量:闭包可以用来创建模块化的代码,保护变量不被外部代码访问,同时提供公共接口来与这些变量交互,放置全局变量污染
-
数据持久化:闭包可以保持函数执行之间的状态,即使函数在不同时间点被调用,它仍然可以访问上次调用时的变量值。
-
延迟执行:闭包可以用于实现异步编程,如定时器和事件处理器,这些场景下闭包可以捕获调用时刻的变量状态。
-
回调函数:在异步操作中,闭包经常用作回调函数,以便在异步操作完成后可以访问调用时的作用域中的变量。
缺点
-
内存泄漏:闭包可以长时间保留变量,闭包写得越多,闭包占据的调用栈越多,导致调用栈的可用空间就会越小,因为JavaScript的垃圾回收机制不会回收仍然被引用的变量。
-
性能考虑:频繁使用闭包可能会影响性能,因为它们增加了内存的负担,并可能使垃圾回收更为复杂。