JS高级03


小概

本章的学习内容:闭包,递归,自执行函数


1. 闭包

1.1 什么是闭包

1.声明在函数内部,能够访问函数内部的局部变量的这么一个函数
2.闭包是函数内部和函数外部的一个连接桥梁
3.闭包是一个不会被销毁的封闭的环境

        function test1(){
            let num = 10; //局部变量的生命周期,函数执行完就回收
            function test2(){
                console.log(num);
            }
            return test2;
        }

        let fn = test1(); //调用test1函数有一个返回值,返回值就是test2函数本身, 用全局变量fn接收. 
        //全局变量生命周期是直到程序关闭的
        fn();

在函数test1中声明的num变量,函数外部是无法访问的,通过闭包(test2拿num,test1返回test2)使外部拿到函数内部声明的变量,而且因为test2调用了num,函数test1执行完后并不会回收变量num,提升了变量的生命周期。

1.2 闭包的作用

1.提升变量的生命周期
2.提供有限的访问权限
3.声明私有属性

        //闭包的作用02: 提供有限的访问权限. 
        function outer(){
            let age = 18;//年龄
            //取值
            function getAge(){
                return age;
            }
            //赋值
            function setAge(value){
                //写判断逻辑代码. 
                if(value >0 && value <= 140){
                    age = value;
                }
            }
            //返回
            return {
                getAge:getAge,
                setAge:setAge
            }
        }

        let obj = outer(); //调用outer会得到一个返回值,返回值就是一个对象,这个对象里面有2个方法,一个getAge,一个setAge
        obj.setAge(28);
        console.log(obj.getAge());

在闭包setAge函数中添加判断条件,没满足条件就不能对age继续操作,提供有限的访问权限

        //2.写一个带私有属性的构造函数. 
        function Student(name,age1){ //形参本质就是函数内部的局部变量.
            let age = age1;
            this.name = name;
            //设置age的值
            this.setAge = function(value){
                if(value > 0 && value <= 140){
                    age = value;
                }
            }
            //获取age的值
            this.getAge = function(){
                return age;
            } 
        }

        let s1 = new Student('德华',22);
        //到这一步我只能通过方法去访问age了
        s1.setAge(40);
        console.log(s1.getAge());

1.3 使用闭包的注意点

如果希望每次使用的局部变量是同一个,那外部的函数只能调用一次.
如果希望每次使用的局部变量不是统一而,那每次都要调用外部函数.

        function outer(){  
            let num = Math.floor(Math.random()*100); //局部变量是0-99之间的一个数. 
            function inner(){
                console.log(num);
            }
            return inner;
        }
        //1.1 三次调用num的值都一样,因为outer只执行一次,那num只声明一次,后面三次都是对他做一个输出而已. 
        let fn = outer();
        fn();
        fn();
        fn();

        //1.2 三次调用num的值都不一样,因为outer每次都执行了,每次都执行都会重新声明一个num,每次输出都是输出那个新的num.
        outer()();
        outer()();
        outer()();

闭包虽然可以提升变量生命周期,但是也可能造成内存泄露,所以退出闭包的时候将不要的变量删除,或者直接赋值为null;闭包应用场景:模块化,封装代码。

1.4 沙箱/沙盒

        //沙箱/沙盒
        //其实就是一个密闭的环境,不能主动影响外界
        //在js中其实就是一个自执行函数. 
        //沙箱其实就是闭包的一个应用. 

        //1.沙箱有什么用? 
        //1.1 避免全局变量污染.
        //1.2 模块化开发

        (function(w){
            let num = 10;

            function test1(){
                console.log(num);
            }

            //把需要暴露给外界的东西暴露出去. 
            //1.避免破坏函数的封装性.
            //2.代码上线的时候会压缩,避免压缩代码出现问题. 
            w.test1 = test1;
            // w.test2 = test2;

        }(window));
        test1();

把闭包需要暴露出去的挂载到window上

2. 递归!

定义:函数内部自己调用自己
递归一定要有结束的时候,不然没有意义(会造成栈溢出)

       //递归的执行过程:  
        let i = 0;
        function test(){
            i++;
            console.log('哈哈'+i);
            if(i<3){
                test();
            }
            console.log('呵呵'+i);
        }
        test();

        //执行结果:
        //哈哈1
        //哈哈2
        //哈哈3
        //呵呵3
        //呵呵3
        //呵呵3

2.2 递归案例

1.用递归求1-n之间的整数累加和

        //分析: 
        //假如n是5. 
        //1+2+3+4     +5
        //1+2+3       +4
        //1+2         +3 
        //1           +2 
        //规律: 如果要求1-n之间的整数累加和,先求1-(n-1)之间的整数累加和,再加n本身.

        //假设我们写了一个函数getSum,就是用来求1-n之间的整数累加和的. 
        //那我们现在调用这个getSum函数求1-5之间的整数和,那在求的过程中,再去调用他求1-4的整数累加和.  
        //那这就是在函数内部调用函数自己,那这不就是递归吗? 
        function getSum(n){
            if(n == 1){
                return 1;
            }
            return getSum(n-1) + n;
        }
        console.log(getSum(5));

2.递归求斐波那契数列第n位是多少

        //2. 用递归求斐波那契数列中第n位是多少? 
        //1 1 2 3 5 8 13 21 34 55 89.....
        //规律: 除了第一位和第二位,以后每一位都是他前两位的和. 

        //假设我们写了一个函数getFB,就是用来求第n位是多少的. 
        //那现在调用这个getFB函数求10位是多少,那在求的过程中就还要去调用这个函数求9位和8位是多少.
        //那这不就是在调用函数的时候,调用了函数自己吗? 那这不就是递归吗? 
        function getFB(n){
            if(n == 1 || n == 2){
                return 1;
            }
            return getFB(n-1) + getFB(n-2);
        }
        console.log(getFB(11));

这个递归求稍后的就会特别卡顿(n=50),因为会重复的调用递归求重复的项,导致第4 5十的时候,函数调用了十几亿次。

递归求斐波那契数列优化

        //思路: 不要求这么多重复的项
        //做法: 把已经求过的项用对象保存起来,下次如果还要求这个项,直接去保存的对象中取出来用.
        let i = 0; //声明一个变量,记录调用函数的次数
        let obj = {};
        function getFB(n) {  
            i++;
            //先判断一下这个n位以前有没有求过. 
            if(obj[n] != undefined){
                //进到这里来,说明以前求过这个n位,那就直接取出来用. 
                return obj[n];
            }else {
                //进到这里来,说明以前没有求过这个n位,那就再求不迟. 
                if(n == 1 || n == 2){
                    obj[n] = 1;//把此时求的存起来. 
                    return 1;
                }else {
                    obj[n] = getFB(n-1) + getFB(n-2);//把此时求的存起来. 
                    return obj[n];
                }
            }
        }
        console.log(getFB(50),i);

还可以通过闭包优化一下,把定义的空对象放在一个函数内,防止其他操作对修改空对象obj

3.递归遍历整个dom元素

        //思路:
        //有一个api是可以查所有子代的: children 
        //可以获取这个元素的所有子代,然后让每一个子代再去查找他们的子代,一直查下去,直到子元素没有子代为止.
        
        //假设我们写了一个函数getHD()就是用来求所有的后代的. 
        //那我们调用这个getHD方法求他的所有后代的时候, 也要去调用这个方法求他的子代的所有后代. 
        //那不就是在函数内部调用函数自己吗? 那不就是递归吗?

        let arr = [];
        function getHD(ele){
            //查到ele元素的所有子代. 
            let children = ele.children;
            //遍历所有的子代
            for(let i = 0; i<children.length;i++){
                let child = children[i]; //一个个的子代.
                arr.push(child);//把求出来的子元素存起来. 
                //每一个子代又要去查找他们各自的后代
                getHD(child);
            }
        }
        //1.求id为father的这个div的所有后代. 
        // let father = document.getElementById('father');
        // getHD(father);
        // console.log(arr);

        //2.遍历整个dom树:拿到页面上所有的标签.  
        getHD(document);
        console.log(arr);

3. 自执行函数补充

自执行函数:函数的自调用
其实就是把函数变成表达式去调用

        //1. 
        // (function test(){
        //     console.log('我是test函数');
        // })();

        //2. 
        // (function(){
        //     console.log('我是函数1');
        // })();

        //3.
        // (function(){
        //     console.log('我是函数2');
        // }());

        //4. 把匿名函数变成一个表达式去调用.
        // !function(){
        //     console.log('函数1');
        // }();


        // +function(){
        //     console.log('函数2');
        // }();

        // ~function(){
        //     console.log('函数3');
        // }();

除了常见的(函数)(),(函数()) !函数() +函数() ~函数() 都可以实现自执行函数,牢记:自执行函数前面必须要带结束符(😉,不然前面的内容会和函数连在一起被解析。

总结

递归?递归?!递归!!!
希望每个人都能:有人爱 有事做 有所期盼。加油在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值