JavaScript之闭包

标签: 闭包  斐波拉契数列  循环注册点击事件出现的问题  循环setTimeout出现的问题  作用域

一、闭包是什么?

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

作用域链:就是函数可以创造作用域,在函数内部也可以声明函数,这就形成了作用域套作用域的链式结构,简称为作用域链。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


版权声明:本文为SpicyBoiledFish原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/SpicyBoiledFish/article/details/72235914

智能推荐

模拟按键 —— 鼠标

背景 之前写自动化脚本的时候总是遇到一些很尴尬的问题: 跑脚本时模拟鼠标按键时,光标是真实的跑到了那个位置的,也就是说跑脚本的时候会很影响电脑的正常使用,导致不得不开一个虚拟机专门跑。 另外因为光标只有一个所以很难实现多线程去同时操作多个窗口,当线程1 模拟鼠标但还没有结束时,线程2 已经开始执行模拟操作,这就导致了线程1 的模拟操作被终止了,被迫之下只能开多个虚拟机(但实在太占用性能🙄) 解决...

Hibernate学习总结(一)

一、Hibernate简介 一个持久层的ORM框架。ORM:Object Relational Mapping(对象关系映射)。指的是将一个Java中的对象与关系型数据库中的表建立一种映射关系,从而操作对象就可以操作数据库中的表。 二、Hibernate入门 1、创建一个项目,引入jar包 hibernate用到的jar包 2、创建表 3、创建实体类 4、创建映射(*****) 映射需要通过XML...

Linux系统NFS

文章目录 1. nfs简介 1.1 nfs特点 1.2 使用nfs的好处 1.3 nfs的体系组成 1.4 nfs的应用场景 2. nfs工作机制 2.1 RPC 2.2 NIS 2.3 nfs工作机制 3. exports文件的格式 4. nfs管理 5. 作业 5.1手动搭建一个nfs服务器 5.1.1开放/nfs/shared目录,供所有用户查阅资料 5.1.2 开放/nfs/upload目...

关于java中String,StringBuffer,StringBuilder的区别以及StringBuffer,StringBuilder的安全性问题

这里的结果就是正确的然后我们来看他的append方法 它在前边加了一个synchronized来修饰,相当于同时只能有一个线程来访问他,这样就不会产生上边的问题但同时他的效率也就比StringBuilder低,...

Django连接现有mysql数据库

1、打开cmd后cd到项目位置 2、建立项目 django-admin startproject test2 3、编辑项目中的配置文件, mysite/settings.py ,告诉Django你的数据库连接参数和数据库名。具体的说,要提供 DATABASE_NAME , DATABASE_ENGINE , DATAB...

猜你喜欢

ShareSDK新浪微博登录时报错error:redirect_uri_mismatch

今天用 ShareSDK 做第三方登录的时候碰到个问题,明明在微博平台的应用审核已经通过了,但是调用登录接口的时候一直报错,错误如下: 出现这个错误是因为在微博开放平台上没有设置回调地址,或者设置的回调地址与本地XML中的地址不一致。 在sharesdk.xml文件当中对于微博的设置: 其中RedirectUrl为设置的回调地址,这里的地址必须要与微博开发平台设置的地址相同,否则就会出现上面的错误...

python解析网络封包方法

2019独角兽企业重金招聘Python工程师标准>>> 在使用Python解析网络数据包时,使用网络字节序解析,参见下表。 C语言的数据类型和Python的数据类型对照表请参见下表。 接下来对封包与解包进行举例说明。 version type id content unsigned short unsigned short unsigned int unsigned int 封包...

python3:时间方法,异常处理,系统文件相关模块(os)

文章目录 时间方法 time模块 时间表示方法: time模块的方法 datetime模块 异常处理 触发异常 创建mydiv.py脚本,要求如下: 创建myerror.py脚本,要求如下: os模块 实现ls -R(os.walk) os.path pickle模块 记账脚本 时间方法 time模块 时间表示方法: 时间戳:自1970-1-1 0:00:00到某一时间点之间的秒数 UTC时间:世...

负载均衡群集——LVS+DR模型

一、实验组成 调度器 192.168.100:41 web1 192.168.100:42 web2 192.168.100.43 NFS共享服务器 192.168.100.44 二、实验拓扑 三、实验配置 3.1在调度器配置:192.168.100.41 配置虚拟IP地址(VIP) 调整/proc响应参数 对于 DR 群集模式来说,由于 LVS 负载调度器和各节点需要共用 VIP 地址,应该关闭...

adb无线连接时appium找不到设备

问题描述 以前使用USB连接真机,运行appium时一直正常,连接参数如下: 最近为了方便,使用adb无线连接真机,adb版本为1.0.40,真机安卓版本10,连接后,通过adb devices能够查看到连接的设备: adb无线连接是正常的,但每次运行时appium都找不到无线连接的设备,陷入重启adb循环: 解决流程 1.因为是没找到设备,所以在appium连接参数中增加了"udid&...