前端提高篇(十九)JS进阶14闭包

前言:
关于闭包的入门篇在这里,初步引入了闭包的概念,在这篇文章中有一个计数器的案例,如果要兼顾计数变量的安全性和计数功能的实现,使用闭包是一个很好的办法
关于计数器的实现方法,这篇文章的最后提供了另一种思路:使用函数的对象特性,巴拉巴拉

正文
闭包三要素
1.嵌套结构的函数
2.内部函数访问了外部函数的变量
3.在外部函数之外,调用内部函数

作用
1.让局部变量变成私有化的长存变量
2.给事件调用的函数传递参数

    <script>
        function f(){
            var x = 1;
            function e(){
                x++;
                console.log(x);
            }
            return e;//返回内部函数的指针,让外部函数之外也能获取到e函数
        }
        var e = f();
        e();//调用一次,计数一次,x自增一次
    </script>
复制代码

或者内部函数赋值给全局变量,扩大作用域,让内部函数可以在大环境下被调用,运行结果同上

    <script>
    	var e;
        function f(){
            var x = 1;
            e = function (){
                x++;
                console.log(x);
            }// 全局
        }
        f();
        e();//调用一次,计数一次
    </script>
复制代码

也可以有多个内部函数,操作外部函数的同一个变量

    <script>
        var e1;
        var e2;
        function f(){
            var x = 1;
            e1 = function (){
                x++;
                console.log(x);
            }
            e2 = function(){
                x--;
                console.log(x);
            }
        }
        f();
        e1();//调用一次,计数一次
    </script>
复制代码

运行结果:
在这里插入图片描述
闭包究竟是什么?
概念:闭包是由函数以及创建该函数的语法环境组成的,这个环境包含了这个闭包创建时所能访问的所有局部变量
就像是有一个简易的类特性:
闭包操作可以看成闭包工厂和闭包对象构成;
由闭包工厂生成闭包对象,每个闭包对象互不干扰;
闭包对象中都存储着工厂给的局部变量,和一个函数,有一个指针指向闭包对象中的函数,使用这个函数;
闭包对象在内存中保存着,直到很长一段时间内都没有使用,系统自动回收;(所以那个x,没有在f函数执行结束之后就失效)
在这里插入图片描述

		//闭包工厂(函数)
        function f(){
            var x = 1;
            function e(){
                x++;
                console.log(x);
            }
            return e;
        }
        var f1 = f();//闭包对象1
        var f2 = f();//闭包对象2
复制代码

运行结果:执行了3次f1后,f1中x的值已经到了4,此时执行一次f2,打印出x的值为2,可以看出,两个闭包对象不干扰
在这里插入图片描述
常见错误:
场景:循环内使用同一个闭包对象,造成变量累加

    <ul>
        <li>li1</li>
        <li>li2</li>
        <li>li3</li>
    </ul>
    <script>
        function showId(id){
            console.log(id);
        }
        function setClick(){
            var ary = document.getElementsByTagName('li');
            for (var i = 0; i < ary.length; i++){
                ary[i].onclick = function(){
                    showId(i+1);
                }
                
            }
        }
        setClick();

    </script>
复制代码

运行结果:不论点击哪一个li,都是输出4
在这里插入图片描述
解析:
setClick函数满足了闭包的要素,里面的匿名函数使用了外部函数创建的变量i,上面代码的for循环,执行之后的结果是:
在这里插入图片描述
可以看到,setClick函数执行后,i已经变成3,并且长存在内存中,所以当我们点击其中一个li,showId出来的,都是4

我们可以通过两种办法解决这个问题
方法一:使用闭包工厂创建多个闭包对象

        function showId(id){
            console.log(id);
        }

        function clickFunc(id){
            function e(){
                showId(id);
            }
            return e;
        }
        function setClick(){
            var ary = document.getElementsByTagName('li');
            for (var i = 0; i < ary.length; i++){
                ary[i].onclick = clickFunc(i + 1);
                
            }
        }
        setClick();
复制代码

运行效果:
在这里插入图片描述
在这里插入图片描述
方法二:使用立即执行函数,创建多个闭包对象

		function showId(id){
            console.log(id);
        }
        
        function setClick(){
            var ary = document.getElementsByTagName('li');
            for (var i = 0; i < ary.length; i++){
                (function (){
                    var id = i;
                    ary[id].onclick = function(){
                        showId(id + 1);
                    }
                })();
            }
        }
复制代码

效果同上:
在这里插入图片描述
调试如下:
在这里插入图片描述

或者不使用闭包

        function setClick(){
            var ary = document.getElementsByTagName('li');
            for (var i = 0; i < ary.length; i++){
                
                    let id = i;
                    ary[id].onclick = function(){
                        showId(id + 1);
                    }
            }
        }
复制代码

效果同上
调试出的各个li的onclick函数也一样

整理一下,应该是,
因为setClick函数里,循环时定义的变量i,在内部函数被直接使用,所以setClick相当于是一个闭包工厂,它的局部变量i是长存的状态,ary[i].onclick在同一个闭包对象中;
当我们点击其中一个li时,showId得到的参数是i+1=3+1=4,而i到达3之后,在闭包中是不再存储累加结果的,所以showId得到的i一直是3;
如果我们将i与id区分开,就能避免i的累加对内部函数参数的影响,即在setClick的内部函数的内部创建了多个闭包对象(记为ID1,ID2,ID3),i依然长存,但不影响闭包对象ID们的参数
循环时,随着i的累加,而创建出闭包对象,i+1已经传到变量id上了
在这里插入图片描述

所以,在使用for循环给内部函数传参时,如果不想要i的累加影响内部函数,简单的办法就是,使用立即执行,或直接用let定义一个变量,隔开内部函数与外部变量的直接联系

如果有更能说服我的理解,我再来更新

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享