JavaScript之闭包

本文详细介绍了JavaScript中的闭包概念,包括其定义、用途及如何通过闭包解决常见问题,如循环点击事件和setTimeout中的变量污染问题。同时,探讨了闭包在优化斐波那契数列计算中的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、闭包是什么?

闭包是可以访问独立变量的函数。因为函数是可以创造作用域的,函数的内部作用域是可以访问外部作用域,但是外部作用域是不能访问内部作用域的,所以在函数内声明的变量可以通过声明的内函数进行访问,然后把这个内函数返回,就可以在函数外面直接访问函数内的独立变量。

作用域链:就是函数可以创造作用域,在函数内部也可以声明函数,这就形成了作用域套作用域的链式结构,简称为作用域链。js分为内部作用域和外部作用域,访问变量都是从内向外沿着作用域链进行查找的。

二、闭包需要解决的问题是什么?

在函数外部访问函数内部声明的变量。

三、闭包的基本模式:

//1.基本模式:
        function outer(){
            var data=10;
            function inner(){
                return data;
            }
            return inner;
        }
        var fn=outer();
        fn();

//2.获取独立变量和设置独立变量:
        function outer(){
            var data=10;
            function getData(){
                return data;
            }
            function setData(value){
                data=value;
                return data;
            }
            return {
                getData:getData,
                setData:setData
            };
        }
        var fn=outer().setData;
        fn(100);   //100

        fn=outer().getData;
        fn();      //10

//3.优化代码
        function outer(){
            var data=10;
            function inner(value){
                if(value){
                    data=value;
                }
                return data;
            }
            return inner;
        }
        var fn=outer();
        fn();

总结起来就是:返回一个函数,用这个函数获得数据;返回一个对象,这个对象包含函数,来操作这个数据


四、循环点击事件存在的问题及解决方案:

代码:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <style>
        div{
            border:solid 1px #000;
            margin-bottom: 10px;
            width:70px;
            height:20px;
        }
    </style>
    <script>
        window.onload=function(){
            var divs=document.getElementsByTagName("div");
            for(var i=0;i<divs.length;i++){
                var div=divs[i];
                div.onclick=function(){
                    console.log("我是第"+i+"个div");
                }
            }
        }

    </script>
</head>
<body>
    <div>第0个div</div>
    <div>第1个div</div>
    <div>第2个div</div>
    <div>第3个div</div>
    <div>第4个div</div>
    <div>第5个div</div>
    <div>第6个div</div>
</body>
</html>

期待的效果是:点击“第0个div”,显示“我是第0个div”,点击“第1个div”,显示“我是第1个div”。但是实际显示的是点击任何一个,显示的都是,“我是第7个div”。



//原因:因为在触发div点击onclick事件的时候,for循环已经执行完毕,这时候返回的i就是7,所以点击每一个div都是返回7

解决办法:使用闭包:将i变量保护起来

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <style>
        div{
            border:solid 1px #000;
            margin-bottom: 10px;
            width:70px;
            height:20px;
        }
    </style>
    <script>
        window.onload=function(){
            var divs=document.getElementsByTagName("div");
            for(var i=0;i<divs.length;i++){
                var div=divs[i];
                function outer(){
                    var j=i;
                    return function(){
                        console.log("我是第"+j+"个div");
                    }
                }
                div.onclick=outer();   //利用闭包解决了循环点击出现的问题
            }
        }

    </script>
</head>
<body>
    <div>第0个div</div>
    <div>第1个div</div>
    <div>第2个div</div>
    <div>第3个div</div>
    <div>第4个div</div>
    <div>第5个div</div>
    <div>第6个div</div>
</body>
</html>



五、for循环中的setTimout出现的问题:

<script>
        for(var i=0;i<10;i++){
            setTimeout(function(){
                console.log(i);
            },0);
        }
        var a=10;
        alert(a);
</script>

setTimout(function(){},time)是至少在time时间后执行回调函数。在js中有分主要任务和次要任务,主要任务就是主逻辑;次要任务就是setTimout和setInterval等回调函数。所以上述代码会先for循环,和setTimeout,执行下面的alert 10,再执行回调函数console.log(i),所以最终返回的也是10次10.

解决办法:利用闭包保护i变量:

<script>
        for(var i=0;i<10;i++){
            function outer(){
                var j=i;
                function inner(){
                    console.log(j);
                }
                return inner;
            }
            setTimeout(outer(),0);
        }
</script>



六、斐波拉契数列的性能问题:

<script>
        //1. 用递归的方法实现斐波拉契数列:
        //增加count计数来计算每次fib函数执行的次数:
        //存在的问题:重复计算的次数太多,
        var count=0;
        function fib(n){
            count++;
            if(n<=2){
                return 1;
            }else{
                return fib(n-1)+fib(n-2);
            }
        }
        fib(5);
        console.log(count);   //9

        count=0;
        fib(20);
        console.log(count);   //13529

        //2. 优化时间复杂度,可以使用非递归的方式实现斐波拉契数列:
        var count1=0;
        function fib1(n){

            var a, b,res;
            a=b=1;
            if(n<=2){
                return 1;
            }else{
                for(var i=3;i<=n;i++){
                    count1++;
                    res=a+b;
                    a=b;
                    b=res;
                }
                return res;
            }
        }
        fib1(20);   //6765
        console.log(count1);   //18

</script>

用递归实现的斐波拉契数列,就存在大量的重复计算,这种重复计算会导致count是成倍成指数型的增加,耗费计算机内存。

除了使用我上面代码中提到的用非递归的方式进行实现,还有一种方法也可以:就是将每次计算出来的fib(n)值存到数组里,这样如果有就直接取,没有就计算,计算好了就存;这样也是大大减少了重复计算量。

//3.利用数组存储的方式优化递归实现的斐波拉契数列算法:
        var arr=[];
        var count2=0;
        function fib2(n){
            count2++;
            var num=arr[n];
            if(num){
                return num;
            }else{
                if(n<=2){
                    return 1;
                }else{
                    num=fib2(n-1)+fib2(n-2);
                    arr[n]=num;
                    return num;
                }
            }
        }
        fib2(20);    //5
        console.log(count2);

除此之外,这个arr变量暴露在全局,很可能被别人随意修改,我们可以通过闭包的方式保护这个变量:

//4.利用闭包优化递归实现的斐波拉契数列算法:(保护了数组变量arr)
        function fib3(){
            var arr=[];
            function fibIn(n){
                var num=arr[n];
                if(num){
                    return num;
                }else{
                    if(n<=2){
                        return 1;
                    }else{
                        num=fib2(n-1)+fib2(n-2);
                        arr[n]=num;
                        return num;
                    }
                }
            }
            return fibIn;
        }
        var fn=fib3();
        fn(5);


七、Github源码地址:

https://github.com/spicyboiledfish/JavaScript-testJS


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值