• JS高级(三):严格模式、闭包、递归、深拷贝和浅拷贝


    一、严格模式

    IE10以上的浏览器才支持
    在这里插入图片描述

    1.开启严格模式

    (1)为脚本开启严格模式

    <script>
       'use strict'
       //下面的js代码就会按照严格模式来执行
    script>
    
    • 1
    • 2
    • 3
    • 4

    或者通过立即调用函数:

    <script>
        (function() {
            'use strict'
        })();  //立即调用函数
        //下面的js代码就会按照严格模式来执行
    script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    (2)为某个函数开启严格模式

    
    <script>
        function fn1() {
            'use strict'
            //里面的代码按照严格模式执行
        }
    
        function fn2() {
            //里面的代码按照普通模式执行
        }
    script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    2.严格模式的一些规定

    (1)禁止变量未声明就赋值

    正常模式下一个变量没有声明就赋值默认是全局变量,而严格模式下变量必须先用var或者let,const声明,然后再使用,否则会报错

    (2)禁止删除已声明的变量

    例如var a = 1; delete a;这种写法会报错

    (3)严格模式下this指向

    在严格模式下全局作用域的函数中,this指向undefined

    <script>
        'use strict'
        //下面的js代码就会按照严格模式来执行
        function fun() {
            console.log(this);  //undefined
        }
        fun();
    script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    注意:在严格模式下的定时器,this还是指向window,只有函数里this有变化

    (4)不允许形参有重名

    function dj(a,a) {  //严格模式下报错
        console.log(a+a);
    }
    
    • 1
    • 2
    • 3

    (5)ES6类中函数默认开启严格模式

    也就是说如果ES6类中的函数如果在外部赋值调用,那么this指向的是undefined

    class App {
        constructor(name) {
            this.name = name;
        }
        
        changeMsg() {
        	//这里其实默认开启了严格模式
            console.log(this);
        }
    }
    
    //搞清楚this的问题
    let app = new App();
    let out = app.changeMsg;
    out(); //undefined
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    (6)在React中babel模式下默认开启严格模式

    在React中,我们写jsx一般都是babel模式,这样的话就会默认开启严格模式,这里面写的不管是全局函数还是ES6里面的函数,默认this都是指向undefined

    (7)不允许在非函数代码块内声明函数

    更多内容请见:MDN关于严格模式的描述

    二、闭包

    1.什么是闭包?

    先复习一下:全局变量、局部变量和作用域链

    闭包:能够访问另一个函数作用域中变量的函数。
    原理:作用域链,当前作用域可以访问上级作用域中的变量

    产生闭包的条件
    1、函数套函数
    2、内部函数访问外部函数局部变量,必须要访问了变量才会产生闭包

    闭包就是将函数内部和函数外部连接起来的一座桥梁。

    什么意思呢?举个例子:

    函数inside访问了函数outside的作用域,那么inside函数就是一个闭包
    function outside() {
        var n = 1;
        function inside() {
            console.log(n);  //1
        }
        inside();
    }
    
    outside();
    console.log(n); //n is not defined
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    关于闭包的定义有些歧义,有的认为inside函数是闭包,有的认为outside里面的函数+环境共同构成闭包,这个不用太纠结。

    2.闭包的作用

    闭包的主要作用:延伸了变量的作用范围

    我们可以借助闭包,实现从外部访问outside函数内部的变量:

    function outside() {
        var n = 1;
        return function() {
            console.log(n); 
        }
    }
    
    let get_n = outside();
    get_n();  //1  外部访问函数内部的变量
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    闭包还有个特点就是,一般来说outside函数调用完变量n就会销毁,但是闭包的话延伸了变量的作用范围outside函数调用完n不会立即销毁,等到get_n函数调用完,n才销毁,即函数作用域中的变量在函数执行结束之后不被销毁

    不过这也导致闭包有个缺点:

    由于垃圾回收器不会将闭包中变量销毁,于是就造成了内存泄露,内存泄露积累多了就容易导致内存溢出。

    3.闭包的一些应用

    先看看什么是立即执行函数:

    function fn(a) { console.log(a) }
    fn(a);
    ===
    (function(a) { console.log(a) })(a);
    
    • 1
    • 2
    • 3
    • 4

    (1)点击li打印当前索引号(var)

    复习:for循环中使用var的问题

    创建一个立即执行函数,这样的话在里面添加点击事件就形成闭包,循环生成lis.length个立即执行函数,每个函数都拿到对应的i,这样的话函数调用完不会马上把当前i销毁,等到点击事件触发完后当前i才会销毁,这样就解决了问题(其实直接用let就可以了)

    不过这样的缺点是四个立即执行函数,内存泄漏和内存溢出问题比较严重

    //应用1:点击li打印当前索引号(用var声明)
    for (var i = 0; i < lis.length; i++) {
        //创建一个立即执行函数,把i传进去
        (function (i) {
            lis[i].onclick = function() {
                console.log(i);
            }
        })(i);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    (2)3秒后打印索引号(var)

    和上面的一个意思,定义多个立即执行函数。
    3秒后才访问变量i,那么i就不会立即被销毁,定时器结束调用后才销毁。

    //应用2:3秒后打印索引号(用var声明)
    for (var i = 0; i < lis.length; i++) {
        //创建一个立即执行函数,把i传进去
        (function (i) {
            setTimeout(() => {
                console.log(i);
            }, 3000);
        })(i);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    (3)用闭包手撕防抖节流

    还不会撕,先占个位

    三、递归

    1.什么是递归?

    1、递归就是函数内部自己调用自己
    2、递归函数的作用和循环差不多,就是无限套娃

    递归很容易会发生栈溢出(不断调用不断开辟内存空间,函数放在堆里,函数的调用结果放在栈里,所以是栈溢出不是堆溢出),所以一定要加return才行。

    递归时函数的执行上下文依次入栈,然后依次出栈调用。

    let num = 1;
    function fn() {
        console.log('打印6句话');
        if (num === 6) {
            return;
        }
        num++;
        fn();
    }
    fn();
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    2.递归函数求阶乘

    利用递归求1~n的阶乘
    这个确实难想……好好理解理解代码吧。
    4*cal(3) => 4*(3*cal(2)) => 4*(3*(2*cal(1))) => 4*(3*(2*1))

    //利用递归求1~n的阶乘
    function cal(n) {
        if(n===1) {
            return 1;
        }
        return n * cal(n-1);
    }
    console.log(cal(4));  //24
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    3.递归求斐波那契数列第几个数是谁

    1、1、2、3、5、8、13、21、34……
    这个也有点不太好想…………

    //利用递归求斐波那契数列第几个数是谁(1,1,2,3,5,8,13,21...)
    function fb(n) {
        if (n === 1 || n === 2) {
            return 1;
        }
        return fb(n - 1) + fb(n - 2)
    }
    console.log(fb(6));
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    执行过程:

    fb(6) 
    => fb(5) + fb(4) 
    => (fb(4) + fb(3)) + (fb(3) + fb(2))
    => ((fb(3) + fb(2)) + (fb(2) + fb(1))) + ((fb(2) + fb(1)) + fb(2))
    => ((fb(2) + fb(1)) + 1) + (1 + 1)) + ((1 + 1) + 1)
    => 3 + 2 + 2 + 1
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    4.递归遍历数据

    先模拟一套后台数据:

    let data = [
        {
            id: 1,
            name:'家电',
            good: [
                {
                    id: 11,
                    gname: '冰箱'
                },
                {
                    id: 12,
                    gname: '洗衣机'
                }
            ]
        },
        {
            id: 2,
            name: '服饰'
        }
    ]
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    实现输入id号就返回对应的数据对象,怎么搞?

    首先forEach遍历,如果是一级数据id直接输出该对象。
    不是一级数据id就要看一下对象里有没有goods数组,有的话要递归调用getId,把goodsid传进去,再输出该对象。

    function getId(data,id) {
        data.forEach(el => {
            if(el.id === id) {
                console.log(el);
            } else if(el.goods) {  //如果有goods数组再递归
                getId(el.goods, id); //递归调用传入good数组
            } 
        });
    }
    getId(data,12);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    四、深拷贝和浅拷贝

    1、浅拷贝只拷贝一层,对象级别只拷贝地址
    2、深拷贝拷贝多层,每一级都会拷贝

    1.浅拷贝

    浅拷贝只拷贝一层,什么意思呢?画个图:
    在这里插入图片描述
    意思就是两个对象(或数组)中引用的都是同一个属性(地址),只不过是外层换了个大地址来存储它们。这样就可以理解为什么浅拷贝只拷贝一层了。里面的东西地址都是一样的。

    先定义两个对象

    const obj = {
        name: 'zzy',
        age: 18,
        grade:{math: 100}
    }
    const copyObj = {}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    把obj浅拷贝给copyObj ,有两种方式

    //1.通过for...in实现浅拷贝
    for(let k in obj) {
        console.log(k) //'name', 'age'
        copyObj[k] = obj[k];
    }
    console.log(copyObj);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    //2.通过Object.assign(target, copywho)实现浅拷贝
    Object.assign(copyObj,obj);
    console.log(copyObj);
    
    • 1
    • 2
    • 3

    由于浅拷贝只拷贝一层,所以objgrade对象拷贝过来的是地址,如果copyObj对象中修改了grade.math,那么obj中的grade.math也会变。

    2.手撕深拷贝

    手撕深拷贝,利用递归实现

    //利用递归手写深拷贝
    function deepCopy(newObj, oldObj) {
        for (let k in oldObj) {
            //判断属性值属于哪种数据类型
            //1.如果是数组(判断数组要在对象前,因为数组也是对象)
            if(oldObj[k] instanceof Array) {
                newObj[k] = [];//赋值空,然后再把数组里的元素一个一个拷贝进来
                deepCopy(newObj[k], oldObj[k]); 
            }
            //2.如果是对象
            else if(oldObj[k] instanceof Object) {
                newObj[k] = {};//赋值空,然后再把对象里的属性一个一个拷贝进来
                deepCopy(newObj[k], oldObj[k]);
            }
            //3.如果是普通数据类型,直接浅拷贝
            else {
                newObj[k] = oldObj[k];
            }
        }
        return newObj;
    }
    deepCopy(copyObj, obj);
    copyObj.grade.math = 99;
    console.log(obj);  //math100
    console.log(copyObj); //math99
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    递归是真tm恶心啊兄弟们

  • 相关阅读:
    JavaScript 进阶 - 第2天
    MySql 主从复制 双机热备 笔记
    有关JavaScript事件循环的若干疑问探究
    App测试时常用的adb命令你都掌握了哪些呢?
    多线程并发、线程池、同步方法、同步代码块(锁)
    【Pytest实战】Pytest 如何生成优美的测试报告(allure-pytest)
    计算机毕业设计(附源码)python优书校园平台
    500代码行代码手写docker-设置网络命名空间
    七月论文审稿GPT第4.5版:通过15K条paper-review数据微调Llama2 70B(含各种坑)
    nextTick()方法的使用
  • 原文地址:https://blog.csdn.net/weixin_42044763/article/details/127892255