javaScript闭包

闭包: 

       前置条件: 词法作用域的规则——内部函数总是可以访问其外部函数中声明的变量

       触发条件: 当通过调用一个外部函数返回一个内部函数后即使该外部函数已经执行结束了,但是内部函数引用外部函数的变量依然保存在内存中,

       结果: 我们就把这些变量的集合称为闭包。

文字有点拗口,我们看代码,有下面一段代码

<script type="text/javascript">
			function foo() {
			    var myName = "zhangsan"
			    let num1 = 1
			    const num2 = 2
			    var innerBar = {
			        getName:function(){
			            console.log('num1:', num1)
			            return myName
			        },
			        setName:function(newName){
			            myName = newName
			        }
			    }
			    return innerBar
			}        
			var bar = foo()
			bar.setName("lisi")
			bar.getName()
			console.log(bar.getName())
</script>

 JS执行方式为 先编译再执行,我们先来看看编译阶段与执行阶段分别发生了什么        

一: 先编译全局代码

  • JS调用栈中有

           全局执行上下文
                    变量环境 
                        foo = function () {...}
                        bar = undefined
                        outer = null

  • JS可执行代码有

           全局可执行代码
                    bar = foo()
                    bar.setName("lisi")
                    bar.getName()
                    console.log(bar.getName())

二:开始执行代码

1.全局代码编译完成此时调用栈如下 

2.执行全局可执行代码到 bar = foo()时, foo是函数,符合一段代码的条件,所以要先编译再执行 

3. 编译foo函数完毕,此时调用栈如下

 foo函数可执行代码有:

                    myName = "zhangsan"
                    num1 = 1
                    num2 = 2
                    innerBar = {
                        getName:function(){
                            console.log('num1:', num1)
                            return myName
                        },
                        setName:function(newName){
                            myName = newName
                        }
                    }
                    return innerBar

4.执行foo函数可执行代码,当foo函数可执行代码全部执行完毕,JS调用栈如下

        

 5.bar = foo()执行完毕。继续往下执行bar.setName("lisi")时,因为setName是函数,符合一段代码的条件。所以需要先编译,再执行。当编译完成时调用栈如下:

此时在JS调用栈中 foo的可执行上下文已经从栈顶弹出, 所以从外部是访问不到myName属性的。
但是根据词法作用域的规则,内部函数 getName 和 setName 总是可以访问它们的外部函数 foo 中的变量,所以就在JS调用栈中开辟了 一个foo函数的闭包,setName 和 getName 方法中使用了 foo 函数内部的变量 myName 和 num1,所以这两个变量依然保存在调用栈的内存中。

通过控制台的Sources的scope栏目可以清晰的看到作用域链的信息

6.setName函数编译完成之后,执行setNmae函数的可执行代码 myName = ‘lisi’, 根据词法作用域可以看出作用域链是  setName的作用域 —》 foo闭包 —》全局作用域。此时再setName的变量环境以及词法环境都没有找到myName变量,所以开始往foo闭包开始找,在foo闭包找到myName 并赋值

7.执行到 bar.getName()时,getName()符合一段代码的标准,需要先编译再执行。且此时setName函数已执行完毕,从调用栈的栈顶弹出,此时调用栈如下

8.getName函数编译完成,执行可执行代码 console.log('num1:', num1) return myName。首先打印num1的时候,再getName的变量环境和词法环境没有找到num1变量,开始往foo找到num1变量同时打印出来,接下来执行return myName 在foo闭包找到myName 并执行 return 'lisi'

9.getName函数执行完成之后,getName执行上下文弹出,开始执行 console.log(bar.getName()),其中getName符合一段代码的条件,先编译再执行,此时调用栈如下

10.getName函数编译完成,执行可执行代码 console.log('num1:', num1) return myName。在foo闭包里找到num1并打印出1,然后执行return 'lisi'。getName执行完毕,执行console.log('lisi')。

11.所以最终是 打印两个1,一个lisi

闭包回收

        当函数执行完毕,该函数的执行上下文就会从调用栈的栈顶弹出,而达到回收的目的

        如果引用闭包的函数是一个全局变量,那么闭包会一直存在,页面关闭才会被释放;但如果这个闭包以后不再使用的话,就会造成内存泄漏,所以如果使用次数少的话,尽量使它成为局部变量

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值