走进作用域与闭包

一:什么是闭包?

  在阮一峰老师的文章中对于闭包的理解是:闭包就是能够读取其他函数内部变量的函数。可能上面这句话大家还是没有明白,那么对于通俗点的理解呢就是:当一个函数用到了作用域外的变量,那么这个变量与这个函数之间的环境叫做闭包

如何产生闭包

  当一个嵌套的内部函数引用了嵌套的外部函数的变量时,就产生了闭包。

产生闭包的条件

  • 函数嵌套
  • 内部函数引用了外部函数的数据

 当我们学习完闭包后,你会发现闭包无处不在。闭包是基于词法作用域书写代码时所产生的自然结果,你甚至不需要为了利用它们而有意识地创建闭包。闭包的创建和使用可以在你的代码中随处可见。你缺少的是根据你自己的意愿来识别和影响闭包的思维环境。

二:作用域的实质性问题

 当函数可以记住并访问所在的词法作用域时,就产生了闭包。下面我们来看一段代码:

function f1() {
	  var a = 1
      function f2() {
          console.log(a)    //函数的内部可以读取外部的局部变量
      }
      f2()		//执行函数就会产生闭包
}
f1()

 我们来观察上面这段代码,首先符合了函数嵌套,其次内部的函数引用了外部函数的变量。
 那么这段是闭包吗?技术上来讲,这段确实是闭包。但更确切的来说,它并不是闭包。为什么这么说呢?在这段代码中函数f2具有一个涵盖函数f1作用域的闭包(因为函数f2嵌套在函数f1的内部),但是上面的这种方式定义的闭包在函数f1的外部并不能被函数外部的对象或者变量所引用,所以我认为上述代码并不是清晰的闭包。那么什么是清晰的闭包呢?下面这段代码清晰的展示了闭包⬇。

	 function f1() {
		 var a = 5
	 	 function f2() {
	         console.log(a)
	     }
	     return f2
	 }
	 var f3 = f1()
	 f3()   //输出5

 上述代码中函数f2的词法作用域能够访问到f1的内部作用域,并且我们将函数f2所引用的函数对象本身当作返回值。并将其返回值赋值给函数f3,当调用函数f3时,实际只是通过不同的标识符引用调用了内部的函数f2。当调用完函数f3时,将会在控制台输出结果为5。

 看完了上面这段代码,它又有什么不一样的地方呢?通常我们在执行f1函数时,我们都希望着f1()的整个作用域被销毁,因为对于我们不再使用的内存空间,javascript的GC垃圾回收机制会将其回收,使其不会再占用内存。

 但是在上面这个闭包的神奇例子中,它与其他函数不同的是:1.它可以在自己定义的词法作用域以外的地方执行。2.由于函数f2对内部作用域依旧具有着引用,使其GC垃圾回收机制不会回收该作用域,该作用域将会一直存活,而且函数f2能够在任何时间使用。

 对于函数f2依旧持有对该作用域的引用,而这个引用就称其为闭包!

那么我们现在有两个问题:

函数执行完毕后,函数声明的局部变量是否还存在呢?

答:一般是不存在的,存在于闭包中的变量才可能存在。

在函数外部能直接访问函数内部的变量吗?

答:不能。但是我们可以通过闭包让外部去操作它。

闭包的缺点

答:1.函数执行完后,函数的局部变量不会被释放,占用内存空间的时间会变长。
  2.容易造成内存泄漏

对于闭包缺点的解决

答:1.能不用闭包就不用闭包
  2.对于闭包需要及时释放,下面为代码:

 		function fn1() {
            var a = 10
            function fn2() {
                console.log(a)
            }
            return fn2
        }
        var fn3 = fn1()
        fn3()
        //将fn3的引用赋值为null,使其成为垃圾对象,使得js的垃圾回收机制回收其闭包
        fn3() = null   

三:闭包的例子

例子1:传递参数

		function showDelay(msg,time) {
            setTimeout(function () {
                alert(msg)
            },time)
        }
        showDelay('closure',2000)    //2秒后控制台输出closure字符串

例子2:传递函数(1)

		function fn1(){
            var a = 2
            function fn2() {
                a++
                console.log(a)
            }
            return fn2
        }
        var fn3 = fn1()
        fn3()  // a:3
        fn3()  // a:4

例子3:传递函数(2)

		var fn

        function fn1() {
            var a = 2
            function fn2() {
                console.log(a)
            }
            fn = fn2
        }

        function fn3() {
            //在fn1函数中,已经将fn2的引用赋给了fn1
            //此时在函数fn中实际执行的是fn1中的fn2函数的内容
            fn()   
        }

        fn1()

        fn3()

四:闭包中的循环

 循环在闭包中似乎是一个非常常见的例子,但刚刚接触循环与闭包时,我们可能会有所疑惑,下面带你走进循环与闭包,先上代码。

<body>
    <button class="show">按钮一</button>
    <button class="show">按钮二</button>
    <button class="show">按钮三</button>
    <button class="show">按钮四</button>
    <button class="show">按钮五</button>
    <script>

        var btns = document.getElementsByClassName('show')
        console.log(btns[1])
        for(var i = 0;i<5;i++){
            btns[i].onclick = function () {
                console.log(i)
            }
        }
        
    </script>
</body>

 上面的代码可以看出我们想要进行的操作是,给每个btn都绑定点击事件,当点击时会在控制台中输出不同的数值,可是结果却跟我们预想的不同。当我们点击不同的btn,其对应的控制台输出的数都为5。
在这里插入图片描述
 这与我们预想的有所不同,我们期望的是在每次迭代时的运行都会给当前btn捕获一个对应的 i 值,但实际情况确是尽管循环中的五个函数是在各个迭代中分别定义的,但是他们都会封装在一个共享的全局作用域中,因此实际上只有一个 i 。

 分析上述问题,上述问题的出现是因为每次的迭代都会共用一个作用域,而不会为其生成一个新的作用域。我们需要为每次的迭代生成一个新的作用域,来去解决上述问题。

解决的代码:

1.创建IIFE来创建作用域

		var btns = document.getElementsByClassName('show')
        for(var i = 0;i<5;i++){
            (function (j) {
                btns[j].onclick = function () {
                    console.log(j)
                }
            })(i)
        }

 使用IIFE能够为每次的迭代生成一个新的作用域,使得内部的绑定函数可以将新的作用域封闭在每个迭代内部,每次迭代都会有一个对应的 i 值传入内部绑定函数中,问题得到了解决。

2.使用ES6中的let声明

		var btns = document.getElementsByClassName('show')
        for(let i = 0;i<5;i++){
            btns[i].onclick = function () {
                console.log(i)
            }
        }

 当在for循环头部中的变量 i 用let声明时,可以在每次迭代中创建一个块级作用域,并在其块级作用域中声明一个对应的变量 i ,问题得到了解决。下面为解决完后操作完成的图片。
在这里插入图片描述
 以上为关于作用域与闭包的理解啦,欢迎评论区指正!
 
 
 有帮助的话就点个赞呗!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值