js 防抖 节流 call apply 知识总结

默认分类 JavaScript

最近学前端,在B站看到了防抖与节流的概念,做个总结(自己理解,可能有误,仅供参考,仅供参考,仅供参考).

防抖与节流概述

防抖: 在执行方法之后,方法并不会真正的执行,在规定的时间内没有再重复执行,这个方法才被真正的执行.
防抖应用场景: input框输入关键字,输入一个字等待一定时间没有再输入后,根据当前输入内容,请求后端接口数据,做内容联想补全提示

节流: 在执行方法后,方法会立刻执行,但是在规定的时间内,方法无法再次执行(类似于技能CD冷却)
节流应用场景: 监听页面滚动,原本的滚动监听方法,滚一下会执行几十到上百次,效率太高,就需要设置时间间隔让它冷却.

实现

其中用到的知识点: 闭包 箭头函数this指向 call

箭头函数

简单来说就是匿名函数优化简写了,但是不能完全随心所欲使用

function(value){
    return value.length;
}
//变成
value => value.length

简写只是一种优点,更多的是解决匿名函数的this指向问题,但这也是一个坑

普通匿名函数this指向他的执行者,箭头函数的this直接就是父级的this

例子

    const aa = {
        a(){
            setTimeout(()=>{console.log(this);},1000) //{a: ƒ}
            setTimeout(function(){console.log(this);},1000) //Window
        }
    }
    aa.a()

所以箭头函数不能通过call来改变this的指向

call apply bind

简单来说call 和 apply是一样的,只有稍微传参差别
都是改变this的指向
bind是直接返回一个绑定this之后的新方法,bind绑定的方法无法再次修改this指向

    window.status = 'Windows的状态'

    function setStatus(status1,status2){
        this.status += `附加${status1}${status2}`
        console.log(this.status);
    }

    const person = {
        status : 'person状态',
        getStatus:()=>{console.log(this.status);},
        getStatus2(){console.log(this.status);},
        getStatus3:function(){console.log(this.status);}
    }

    console.log(person); // {status: 'person状态'}
    //直接执行,this就找到顶级作用域window了
    setStatus('晕眩','致盲') // Windows的状态附加晕眩致盲
    //绑定到了person,this就是person
    setStatus.call(person,'晕眩','致盲')  // person状态附加晕眩致盲
    //apply相较于call只是传参不同
    setStatus.apply(person,['晕眩','致盲'])  // person状态附加晕眩致盲附加晕眩致盲
    //把方法绑定到person后返回一个方法供调用
    let setPersonStatus = setStatus.bind(person);
    //调用传参
    setPersonStatus('治疗','减伤');  // person状态附加晕眩致盲附加晕眩致盲附加治疗减伤
    //重复多次调用
    setPersonStatus('治疗1','减伤1'); // person状态附加晕眩致盲附加晕眩致盲附加治疗减伤附加治疗1减伤1
    console.log(person); //{status: 'person状态附加晕眩致盲附加晕眩致盲附加治疗减伤附加治疗1减伤1'}
    //箭头函数this对比
    //箭头函数
    person.getStatus()  // Windows的状态附加晕眩致盲
    //简写的正常函数
    person.getStatus2()  // person状态附加晕眩致盲附加晕眩致盲附加治疗减伤附加治疗1减伤1
    //正常函数
    person.getStatus3()  // person状态附加晕眩致盲附加晕眩致盲附加治疗减伤附加治疗1减伤1

利用这个call取巧操作 仅作理解示例,真是应用起来某些可能脱裤子xx

比如document.querySelectorAll('li') 这样选出了一个NodeList,里面没有自带的slice方法做切割,可以借用数组里面的slice方法来切割NodeList.

//获得nodelist
let nodeList = document.querySelectorAll('li')
console.log(nodeList); //NodeList(5) [li, li, li, li, li]

//借用数组的slice方法掐头去尾切割nodelist
let newLiArr = [].slice.call(nodeList,1,-1)
console.log(newLiArr); //(3) [li, li, li]

//甚至这样直接转数组
let liArr = [].slice.call(nodeList)
console.log(liArr); // (5) [li, li, li, li, li]


    //给nodelist添加数组的slice方法 bind时方法直接就把this绑定死了,后面无法修改
    nodeList.slice = [].slice.bind(nodeList) //正常的话相当于 nodeList.slice = [].slice 但此处是硬绑定this指定死了是nodeList之后再调用call也无法修改this指向
    //正常切割
    console.log(nodeList.slice(1, -1)); //(3) [li, li, li]
    //获取a标签nodelist
    let a = document.querySelectorAll('a');
    console.log(a); //NodeList(5) [a, a, a, a, a]
    //此处借用上面nodelist的slice方法切割a,但还是返回的切割结果是上面nodelist自己的,所以bind之后无法再次修改this指向
    console.log(nodeList.slice.call(a,1,-1)); //[li, li, li]
    a.slice = nodeList.slice
    //直接方法赋值也无法修改
    console.log(a.slice(1,-1)); //[li, li, li]
    //再次bind也无用
    a.slice = nodeList.slice.bind(a)
    console.log(a.slice(1,-1)); //[li, li, li]

数组合并

    let a = [1, 2, 3];
    let b = [4, 5, 6];
    [].push.apply(a, b); // 相当于 a.push(4,5,6);
    [].push.call(a, ...b);// 相当于 a.push(4,5,6);
    console.log(a); //(9) [1, 2, 3, 4, 5, 6, 4, 5, 6]
    //把数组的push方法绑定到a上之后返回 arrPush就是a.push
    let arrPush = [].push.bind(a);
    arrPush(7, 8, 9); // a.push(7,8,9);
    console.log(a); // (12) [1, 2, 3, 4, 5, 6, 4, 5, 6, 7, 8, 9]

防抖实现

    //fun 真实执行的函数 delay延时时间
    function fangdou(fun, delay) {
        //设置一个初始值
        let timer = null;
        //返回一个函数 此处也老老实实用function 不能用箭头函数 以确保里面的this是真实执行者
        return function(){
            //如果不是初始值,那就肯定是定时器,就清掉
            if (timer !== null){
                clearTimeout(timer);
            }
            //重新设置定时器
            timer = setTimeout(()=>{
                //设置this指向真实执行者
                fun.call(this);
            },delay)
        }
    }

    //选中input框,添加一个keyup的事件监听  此处不能使用箭头函数,老老实实的用function 这样this才受控,上面能用call改变指向
    document.querySelector('input').addEventListener('keyup',fangdou(function(){
        console.log(this.value);
    },1000))

上面代码就是输入框输入文字后停顿1秒 会打印输入的内容.

addEventListener会在最开始解析的时候就执行了fangdou方法以获取结果值,是一个function,然后再每次触发监听事件的时候再执行这个结果值.
就像

    function say(){
        console.log('say');
    }

    function callSay(){
        return function(){
            console.log('say');
        }
    }
    
    function callSay2(){
        return say;
    }

    //一开始就执行 延时未生效
    setTimeout(say(),1000);
    //以下正常延时1秒输出
    setTimeout(say,1000);
    setTimeout(callSay(),1000);
    setTimeout(callSay2(),1000);

以前我用setTimeout踩过坑,延时不生效

节流实现

大同小异 此处没有this指向需求就简写使用箭头函数了

    function jieliu(fun, delay) {
        //定义是否可以允许
        let run = true
        return () => {
            if (run) {
                //执行方法
                fun();
                //设置不可运行
                run = false;
                //设置定时器延时delay之后改为可以运行
                setTimeout(() => { run = true }, delay)
            }
        }
    }
    //添加页面上下滚动监听事件 cd冷却1秒
    window.addEventListener('scroll', jieliu(() => {
        console.log((new Date).toUTCString());
    }, 1000))

新评论

称呼不能为空
邮箱格式不合法
网站格式不合法
内容不能为空