JavaScript中的闭包是一个非常重要且强大的概念。它使得我们能够在函数内部创建一个独立的作用域,同时仍然可以访问外部作用域中的变量。闭包不仅可以帮助我们实现私有化变量,还可以用于封装和模块化代码。但同时,闭包也可能导致内存泄漏的问题,因为它会持续保留对外部作用域中变量的引用,如果不注意及时释放,就会占用过多的内存空间。
调用栈
在理解闭包的概念之前,我们先来看一下调用栈。调用栈是用来管理函数调用关系的一种数据结构。 在JavaScript中,每当函数被编译时,都会创建一个称为“执行上下文”的内部对象,并被压入调用栈的底部。
当函数执行结束后,执行上下文将被弹出调用栈。清除函数栈的内存。(叉代表清除函数出栈)
这种调用栈的机制使得函数调用能够按照预期顺利进行。
若是函数执行完成却没有清除并反复调用就会爆栈:
function foo() {
foo()
}
foo()
//函数爆栈
作用域链
作用域链是另一个重要的概念,它通过词法环境来确定某个作用域的外层作用域。在JavaScript中,作用域链决定了变量的查找路径,即变量由内而外的这种链状关系。当一个函数内部访问某个变量时,JavaScript引擎会根据作用域链来查找变量的值。
在JavaScript里面会存在一个变量outer,当函数被编译outer被赋的值就是函数的词法环境,它会指向外层作用域,当outer在全局作用域时outer=null。
如下图foo()里面存在outer用来指向全局作用域,bar()里面存在outer也用来指向全局作用域,当函数执行时会通过outer直接去全局作用域寻找变量。
闭包
闭包则是基于作用域链的概念而来的。在JavaScript中,内部函数总是能够访问外部函数中的变量。当通过调用一个外部函数返回一个内部函数后,即使外部函数已经执行完毕,但是内部函数引用了外部函数中的变量,这些变量依然会保存在内存中,形成了闭包。闭包使得内部函数可以继续访问外部函数的变量,即使外部函数已经执行结束。
如下图调用栈解释代码
function foo(){
var myName = '阿美'
let test1 = 1
const test2 = 2
var innerBar = {
getName:function(){
console.log(test1);
return myName
},
setName:function(newName){
myName = newName
}
}
return innerBar
}
var bar = foo();
bar.setName("洋洋");
console.log(bar.getName());
当第16行代码foo的调用执行完毕,系统应该将foo从函数调用栈清除,但是第17行setName被调用时它的外层作用域是foo,那么setName的outer应该指向foo的词法环境,所以foo不能完全销毁,会留下一部分称为“包”,其余函数会调用这个包。