【JavaScript】11-JS高阶技巧

本文介绍JS中的一些高阶技巧。

目录

1. 深浅拷贝

1.1 浅拷贝

1.2 深拷贝

1.2.1 通过递归实现

1.2.2 lodash / cloneDeep

1.2.3 JSON.stringify()

2. 异常处理

2.1 throw 抛异常

2.2 try / catch 捕获异常

2.3 debugger

3. 处理this

3.1 this指向

3.1.1 普通函数this

3.1.2 箭头函数的this

3.2 改变this

3.2.1 call方法改变

3.2.2 apply方法改变

3.2.3 bind方法改变

4. 防抖 debounce

4.1 lodash 

4.2 手写防抖

5. 节流 throttle

5.1 lodash库

5.2 手写节流

5.3 节流与防抖总结


1. 深浅拷贝

开发中经常需要复制一个对象,如果直接复制会有以下问题:

    <script>
        const obj = {
            uname : 'zzz',
            age : 18
        }
        const o = obj;
        console.log(o);

        // 修改o
        o.age = 20;
        console.log(o);
        console.log(obj);  // 20  obj的值也被修改了
    </script>

给o的复制相当于把地址给了o,修改后对obj也修改了


1.1 浅拷贝

首先深浅拷贝只针对引用类型

浅拷贝拷贝的是地址

常见方法:

    <script>
        const obj = {
            uname : 'zzz',
            age : 18
        }
        const o = {...obj};  
        console.log(o);
        o.age = 20;
        console.log(o);
        console.log(obj);  // 这里没有被改变 仍是18
    </script>
    <script>
        const obj = {
            uname : 'zzz',
            age : 18
        }

        const o = {}
        Object.assign(o,obj);
        o.age = 20;
        console.log(o);
        console.log(obj);  // 没有被改变 18
    </script>

但是只是对于简单的不会被改,比如下面的问题

    <script>
        const obj = {
            uname : 'zzz',
            age : 18,
            family : {
                baby : 'bbb'
            }
        }

        const o = {}; //定义对象
        Object.assign(o,obj);
        o.family.baby = 'aaa';
        console.log(o);
        console.log(obj);  // 这里的baby被修改了
    </script>

内层的仍然被修改,内层拷贝的仍然是地址,只有外层的是只拷贝了值

所以是拷贝


1.2 深拷贝

只针对引用类型

深拷贝:拷贝的是对象 而非地址

常见方法:

1.2.1 通过递归实现

如果一个函数在内部可以调用其本身,就是递归函数

即 自己调用自己 类似循环

由于递归容易发生栈溢出错误,所以必须要加退出条件 return

    <script>
        let i = 1;
        function fn(){
            console.log(`这是第${i}次`)
            if(i>=6){
                return    // 退出
            }
            i++;
            fn();  // 调用自己 递归 容易死递归 栈溢出
            
        }
        fn(); // 外部先调用才能开始
    </script>

利用递归函数实现 setTimeout 模拟 setInterval 效果

需求:

① 页面每隔一秒输出当前时间

② 输出时间用 new.Date().toLocaleString()

<body>
    <div></div>
    <script>
        function getTime(){
            document.querySelector('div').innerHTML = new Date().toLocaleString();
            // 每隔一秒调用一次这个函数
            setTimeout(getTime,1000);
        }
        getTime();
    </script>
</body>

数组情况:

    <script>
        const obj = {
            uname : 'zzz',
            age : 18,
            hobby: ['乒乓球','足球']
        }
        // 创建新对象
        const o = {}
        // 拷贝函数
        // 利用遍历 把每个值取出复制
        function deepCopy(newobj,oldobj){
            // for in 方法遍历对象  旧obj
            for(let k in oldobj){
                // 处理数组问题
                if(oldobj[k] instanceof Array){
                    // 遍历数组 
                    // 递归调用自己来实现复制
                    // 先创建一个新数组
                    newobj[k] = [];
                    deepCopy(newobj[k],oldobj[k]);  // 传入的参数是数组
                }
                // 非数组情况
                else{
                    // k是属性名 oldobj[k]是属性值 这里只复制值
                    newobj[k] = oldobj[k];
                    // newobj[k] === o.uname
                }
            }
        }
        deepCopy(o,obj);  // o新 obj旧
        // 本质还是浅拷贝
        o.hobby[0] = '篮球';
        console.log(obj);  // 此时没有被修改

    </script>

在这里注意 newobj[k] 是[ ]存的变量k

如果对象里还有嵌套对象 可以仿照数组的形式

加上以下内容即可

                // 处理对象问题
                else if(oldobj[k] instanceof Object){
                    // 遍历对象 
                    // 递归调用自己来实现复制
                    // 先创建一个新对象
                    newobj[k] = {};
                    deepCopy(newobj[k],oldobj[k]);  // 传入的参数是数组
                }

注意:

一定先写数组后写对象,因为数组也是对象,不然操作出现错误


1.2.2 lodash / cloneDeep

JS库的lodash里面cloneDeep

<body>
    <!-- 先引用lodash库,这里仅做使用演示,实际没有文件 -->
    <script src="./lodash.in.js"></script>
    <script>
        const obj = {
            uname : 'zzz',
            age : 18,
            hobby: ['乒乓球','足球'],
            family: {
                baby:'zzz'
            }
        }
        const o = _.cloneDeep(obj);
        console.log(o);
        o.family.baby = 'aa';
        console.log(obj);
    </script>
</body>

1.2.3 JSON.stringify()

    <script>        
        const obj = {
            uname : 'zzz',
            age : 18,
            hobby: ['乒乓球','足球'],
            family: {
                baby:'zzz'
            }
        }
        // 将对象转为JSON字符串
        // console.log(JSON.stringify(obj));
        // 转为字符串后变为简单数据类型 和对象没有关系了

        // 将JSON字符串转为新对象
        const o = JSON.parse(JSON.stringify(obj));
        console.log(o);
    </script>

对象 —— 字符串 —— 新对象

新对象和之前的对象没有任何关系


2. 异常处理

了解JS中程序异常处理的办法 提升代码运行的健壮性

2.1 throw 抛异常

异常处理是指预估代码执行过程中可能发生的错误,然后最大程度的避免错误的发生导致整个程序无法继续运行

    <script>
        function fn(x,y){
            if(!x || !y){
                // console.log(11);  // 没有传值 
                // throw '没有参数传递';
                // throw会中断程序运行
                // 一般搭配 new Error 使用
                throw new Error('没有参数传递过来');
            }
            return x+y;
        }
        console.log(fn());
    </script>

2.2 try / catch 捕获异常

try先判断是否有错,有错就 catch 拦住  finally 最后执行什么

<body>
    <p>123</p>
    <script>
        function fn(){
            // 将容易犯错的部分写到try部分
            try{
                const p = document.querySelector('.p');
                p.style.color = 'red';
            } catch(err) {
                // err保存浏览器提供的错误信息
                console.log(err.message);  // message是信息
                // 前面的标签不能有.   
                // 报错信息:Cannot read properties of null (reading 'style')
                return 
                // 退出
            }
            finally {
                // 不管对不对都会执行的代码
                alert('zzzz');
            }
            console.log(11);  
            // 没有return时 能输出 说明try catch不会中断 需要return 或者搭配throw使用
        }
        fn();
    </script>
</body>

2.3 debugger

在某位置加上debugger后

打开Sources 刷新后自动跳到debugger位置


3. 处理this

3.1 this指向

3.1.1 普通函数this

普通函数的调用方式决定了this的值,即谁调用this的值就指向谁

<body>
    <button>点击</button>
    <script>
        console.log(this);  // window

        function fn(){
            console.log(this);  // window
        }
        fn();

        setTimeout(function(){
            console.log(this);
        },1000);   // window

        document.querySelector('button').addEventListener('click',function(){
            console.log(this);  // button
        })
        
        const obj = {
            sayHi : function(){
                console.log(this);   // obj
            }
        }
        obj.sayHi();
    </script>
</body>

3.1.2 箭头函数的this

箭头函数于普通函数完全不同,也不受调用方式影响,事实上箭头函数并不存在this

1. 箭头函数会默认绑定外层的this,所以箭头函数的this和外层的this一样

2. 箭头函数的this引用的时最近作用域的this

3. 向外层的作用域中,一层一层查找this,直到有this的定义

注意不推荐的:

1. DOM事件中如果需要DOM对象里的this 不推荐使用箭头函数

2. 原型对象不推荐使用箭头函数 否则this不指向实例对象了 


3.2 改变this

3.2.1 call方法改变

call调用函数,同时指定被调用函数中的this的值

语法:

fun.call(thisArg, arg1, arg2...)

thisArg:在fun函数运行时指定的this值

arg1 arg2:传递的其他参数

返回值就是函数的返回值,因为它是调用函数

    <script>
        const obj = {
            uname : 'zzz'
        }
        function fn(x,y){
            console.log(this);  // window
            console.log(x+y);
       } 
       // 1.调用函数
       // 2.改变this指向
       fn.call(obj,1,2);  // 指向window 变为 指向obj
       // x给1,y给2 代表其他操作
    </script>

3.2.2 apply方法改变

apply调用函数,同时指定被调用函数中的this的值

语法:

fun.call(thisArg, [argsArray])

thisArg:在fun函数运行时指定的this值

argsArray:传递的其他参数必须包含在数组内

返回值就是函数的返回值,因为它是调用函数

apply主要和数组有关,比如调用Math.max()求数组最大值

    <script>
        const obj = {
            uname : 'zzz'
        }
        function fn(...Array){
            console.log(this);
            console.log(Array);
        }
        // 1.调用函数  2.改变this指向
        fn.apply(obj,[2,3,1,6,19,11]);
        // 返回值 本身就是调用函数 返回值就是函数返回值

        // 求数组最值
        const max = Math.max.apply(Math,[1,2,3,5])  // 首先指向Math
        const min = Math.min.apply(Math,[1,2,3,5])  // 首先指向Math
        console.log(max,min);

        arr = [1,2,3,4];
        console.log(Math.max(...arr));
    </script>

3.2.3 bind方法改变

bind() 不会调用函数 但是可以改变内部this指向

语法:

fun.bind(thisArg, arg1, arg2...)

thisArg:在fun函数运行时指定的this值

arg1 arg2:传递的其他参数

返回由指定的this值和初始化参数改造的原函数拷贝(新函数)

只想改变this指向 但是不想调用函数的时候bind()

    <script>
        const obj = {
            uname : 'zzz'
        }
        function fn(){
            console.log(this);
        }
        // bind不会调用函数
        // 能改变this指向
        // 返回的是个函数 但是里面的this是改后的
        const fun = fn.bind(obj);
        // console.log(fun);
        fun();
    </script>

改变定时器内部的 this 指向

<body>
    <button>发送</button>
    <script>
        // 需求:有一个按钮,点击就禁用,2s后开启
        const btn = document.querySelector('button');
        btn.addEventListener('click',function(){
            this.disabled = true  // 禁用吗?true  this是按钮
            setTimeout(function(){  // window调用了计时器
                // 普通函数中由原来的window改为btn
                this.disabled = false;  // btn开启
            }.bind(this),2000)  // this指向btn  和上面的this指向的都是btn 所以this即可
        })
    </script>
</body>

4. 防抖 debounce

单位时间内,频繁触发事件,只执行最后一次。

使用场景:

搜索框搜索输入字符时,只需要用户输完最后一个字,再发送搜索请求。

例如:

利用防抖处理-鼠标滑过盒子就显示文字

鼠标在盒子上移动,里面的数字会变化+1

方法:

4.1 lodash 

_.debounce(func, [wait = 0], [options=])

该函数会从上一次被调用后,延迟wait毫秒后调用func方法

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        .box{
            width: 300px;
            height: 500px;
            background-color: #ccc;
            color: #fff;
            text-align:center;
            font-size: 100px;;
        }
    </style>
</head>
<body>
    <div class="box"></div>
    <script src="./lodash.min.js"></script>
    <script>
        // 防抖实现性能优化
        // 需求:鼠标在盒子上移动 里面的数字就变化+1
        // 首先获取盒子
        const box = document.querySelector('.box')
        let i = 1;  // 设置初始
        function mouseMove(){
            box.innerHTML = i++;
            // 如果存在大量消耗性能的代码 如DOM 数据处理等 可能造成卡顿
            // 鼠标在盒子上停留500ms后 里面数字再加一

        }
        // 添加滑过事件
        // box.addEventListener('mousemove',mouseMove)

        // 1.lodash库实现防抖 500ms后+1
        // box.addEventListener('mousemove',_.debounce(mouseMove,500));
    </script>
</body>
</html>

4.2 手写防抖

防抖核心:setTimeout实现

① 声明一个计时器变量

② 当鼠标每次滑动都先判断是否有定时器了,如果有定时器先清除以前的定时器

③ 如果没有以前的定时器就开启定时器,存到变量里

④ 定时器里调用要执行的函数

        // 2.手写防抖效果
        // 防抖核心:setTimeout实现
        // 1.声明一个计时器变量
        // 2.每次鼠标移动都先判断是否有定时器,如果有先清除以前的定时器
        // 3.如果没有以前的定时器就开启定时器,存到变量里
        // 4.定时器里实现函数调用
        function debounce(fn,t){
            let timer;  // 声明计时器变量
            // return 返回一个匿名函数 相当于将这个匿名函数给了下面的debounce
            // 每触发一次事件 下面的debounce就会接收一个function
            return function(){
                if(timer) clearTimeout(timer);  // 是否有定时器 清除
                timer = setTimeout(function(){  // 开启定时器
                    fn();  //小括号调用
                },t)
            }
        }
        box.addEventListener('mousemove',debounce(mouseMove,500)); 
        // 每次鼠标经过时就会 debounce - function() 

5. 节流 throttle

节流:单位时间内,频繁触发事件,只执行一次

防抖是执行最后一个,节流是只能执行第一个

只有本次执行结束后才能执行下一次

使用场景:

高频事件:鼠标移动 mousemove、页面尺寸缩放 resize、滚动条滚动 scoll 

仍然是上面的例子,鼠标在盒子上滑动,不管移动多少次,每隔500ms+1

5.1 lodash库

_.throttle(func, [wait = 0], [options = ])

在wait秒内最多执行func一次的函数

    <div class="box"></div>
    <script src="./lodash.min.js"></script>
    <script>
        // 节流实现性能优化
        // 需求:鼠标在盒子上移动 里面的数字就变化+1
        // 首先获取盒子
        const box = document.querySelector('.box')
        let i = 1;  // 设置初始
        function mouseMove(){
            box.innerHTML = i++;
            // 如果存在大量消耗性能的代码 如DOM 数据处理等 可能造成卡顿
            // 鼠标在盒子上停留500ms后 里面数字再加一

        }

        // 1.lodash
        box.addEventListener('mousemove',_.throttle(mouseMove,500))
    </script>

5.2 手写节流

节流核心:setTimeout实现

① 声明一个计时器变量

② 当鼠标每次滑动都先判断是否有定时器了,如果有定时器则不再开始新定时器

③ 如果没有以前的定时器,就开启定时器,存到变量里

— 定时器里调用执行的函数

— 定时器里要把定时器清空  时间到了才开启下一个新的定时器

④ 定时器里调用要执行的函数

        function throttle(fn,t){
            let timer = null;  //声明一个计时器变量
            return function(){
                if(!timer){
                    timer = setTimeout(function(){
                        fn();
                        // 清空定时器 才能开启下一个新的定时器
                        timer = null;  
                        // 在定时器里面 不使用clearTimeout 在开启定时器的里面再关闭是不对的
                    },t)
                }
            }
        }
        box.addEventListener('mousemove',throttle(mouseMove,1000))

5.3 节流与防抖总结


本文介绍JS中的一些高阶技巧。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值