浅谈调用栈和闭包
何为调用栈
用来管理函数调用关系的一种数据结构,当一个函数执行完毕后,该函数的执行上下文就会被销毁(出栈)
var a=2
function add(){
var b = 10
return a + b
}
add()
这就是该该函数调用时,引擎会维护出一个调用栈,如图。
变量环境和词法环境都是 JavaScript 中用来存储变量和函数的数据结构,它们都属于执行上下文的一部分。 执行上下文是JavaScript 代码执行时的环境,它包含了变量、函数、this、外部环境等信息。
变量环境是一个词法环境,它用来存储通过 var 声明或 function 声明的变量和函数。 变量环境有一个外部引用,指向外层的词法环境,形成了作用域链。 变量环境也有一个环境记录器,用来记录变量和函数的绑定关系。
词法环境也是一个词法环境,它用来存储通过 let、const、with、try-catch 等语句创建的变量和函数。 词法环境也有一个外部引用,指向外层的词法环境,形成了作用域链。]词法环境有两种类型的环境记录器:声明式环境记录器和对象式环境记录器。声明式环境记录器用来记录通过 let、const、function 等声明的变量和函数的绑定关系。 对象式环境记录器用来记录通过 with 语句或全局对象创建的变量和函数的绑定关系。
当add这个函数执行完之后呢,它的执行上下文就会被销毁。如图
这就是调用栈的作用。
何为闭包
-
形成:当函数A将内部函数B返回出来调用时。
-
定义:当通过调用外部函数返回的内部函数后,即使外部函数已经执行结束了, 但是内部函数引用了外部函数的变量依然会保存在内存中,我们把这些变量的集合, 称为闭包。
我们直接上例子!
function a(){
function b(){
var bbb=234
console.log(aaa)
}
var aaa=123
return b
}
var demo=a()
demo()
我们可以先看看这段代码。 调用栈为
首先 调用函数 a 并把返回值赋给变量 demo
然后 调用变量 demo,即函数 b
但是调用这个函数是放在a函数之外来调用的。
根据我们所了解的知识:内层作用域能访问外层作用域,但是外层作用域访问不了内层作用域。
所以b函数这里是在a函数外面调用的所以用不了a里面的aaa ,b函数里面又没有aaa变量,全局作用域也没有aaa变量所以打印这个肯定会报错。然而
这个结果出乎我们的意料!为什么会这样呢?那就得来聊一聊今天的主角 闭包了
闭包
是当通过调用外部函数返回的内部函数后,即使外部函数已经执行结束了, 但是内部函数引用了外部函数的变量依然会保存在内存中,我们把这些变量的集合, 称为闭包。
这里显然符合闭包的形成条件,当函数a将内部函数b返回出来调用时会形成闭包。根据上面闭包的定义,我们可以知道这个闭包里面是什么>>>内部函数引用了外部函数的变量,显然在这里就是>>>aaa =123!这个数据就会形成闭包保存在内存中可以被b函数访问,不会在a函数执行完毕后被销毁。如图
a的执行上下文摧毁后会产生闭包。并且b函数在调用是可以访问到这个闭包中的变量。 这就是为什么打印出来的值是123。
闭包的作用
1.模块化开发(实现公有变量)
2.做缓存
3.封装私有化属性
4.防止全局变量污染
闭包的缺点
一旦形成闭包只有在页面关闭后闭包占用的内存才会被回收,所以造成了内存泄漏。
- 闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。
- 闭包可能会导致变量的值被意外修改或污染,因为闭包的变量可以被外部函数访问和修改,这可能会造成一些逻辑上的错误或难以调试的问题
为了避免闭包的缺点,可以采取以下一些措施:
- 及时释放不需要的闭包,比如使用完毕后将引用置为 null ,或者使用 let 和 const 代替 var 来声明变量,避免变量提升到全局作用域。
- 尽量不要在闭包中保存大量的数据或对象,以减少内存占用。
- 尽量不要在闭包中修改父级函数的变量,以防止变量被污染或产生副作用。
结语
关于闭包的知识这里就分享完啦!欢迎来到评论区纠正,补充!