美文网首页基础前端
debounce and throttle

debounce and throttle

作者: CondorHero | 来源:发表于2020-06-27 00:04 被阅读0次

    前言: 整个六月份就水了四篇文章,这完美的证明了我六月份基本没学啥东西 😤。这周在工作中主要是写了消息系统模块,自己封装的,测试老发现 bug ,只能发现一个修一个,还好是小姐姐,不然都要过来揍我了,新项目可的仔细点,多看 PRD 多测试。

    主题:工作中我小伙伴喜欢用 lodash 这个库,我就喜欢 ES6 ,因为在我认识中,ES6 处理数据已经挺完美了,而且一个高手还能把处理数据的代码全给压平,代码结构由立体变扁平,类似这种:

    //本人项目截取片段,写的不是很好多包涵
    let unReadHeadIdArr = res.data.messages.map(item=>item && item.head.id);
    let allMessArrId = window.sessionStorage.getItem("allMessVisable");
    allMessArrId = allMessArrId ? JSON.parse(allMessArrId) : [];
    let unReadHeadId = unReadHeadIdArr.filter(item=>!allMessArrId.includes(item));
    

    其实我也是不想看 lodash,immediate这种第三方库,那么多 API,emmm,但是 ES6 也不是完美无缺的,其中就少了一个很重的东西,防抖和节流函数。而恰恰这两个函数在项目中很常用到。这时候就能体验到 lodash 的便捷了,虽然它的代码体积很大。最近我在写登陆模块的按钮的时候就体会很深,想起我以前技术差的时候能把项目模块功能写出来就很 happy 了,现在开始有点精力去注意这些边边角角了,that's brilliant。本着学习的态度,当然的研究下 debounce and throttle 的源码了,因为如果不使用 lodash 第三方库的时候,我们可以在项目中 util 文件夹中,放一些项目工具函数的地方,引入这两个函数,然后愉快的使用。

    underscorelodashjs 这两个库都有 debounce and throttle 用法都差不多,就以 underscore 为例进行研究吧。

    先推荐一个视频教程看看思路:手写函数防抖和节流——小马哥_老师
    还有一个文字版的教程也可以看看:underscore 函数去抖的实现

    在去官网看看 debounce 和 throttle 的用法:

    我了解的防抖和节流:

    • 防抖:多次触发,只执行一次。
    • 节流:一直触发期间合理执行。

    这是个 underscore CDN 可以打开对照源码阅读:underscore

    一、基本骨架

    鼠标移动无限次触发

    鼠标移动无限次触发计数显示:

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>debounce and throttle</title>
        <style>
            div{
                height: 300px;
                width: 900px;
                margin: 50px auto;
                display: flex;
                justify-content: center;
                align-items: center;
                color: #fff;
                font-size: 38px;
                background-color: #222;
            }
        </style>
    </head>
    <body>
        <div id="app"></div>
        <script>
            let count = 0;
            app.onmousemove = function(){
                this.innerHTML = count++;
            }
        </script>
    </body>
    </html>
    

    二、debounce 实现

    underscore 中 debounce 函数有三个参数:debounce(需要防抖的函数,间隔时间,执行顺序)

    首先讲下实现的三个难点:

    • 需要防抖函数中的 this;
    • 需要防抖函数的事件对象 event ;
    • 需要防抖函数返回结果不能改变

    一版:能防抖、能绑定 this 和 event

    <script>
        let count = 0;
        // debounce是一个高阶函数
        function debounce(func,wait){
            let timeout,context;
            return function(...args){
                // 这个函数里面的this就是要防抖函数要的this
                //args就是事件对象event
                context = this;
    
                // 一直触发一直清除上一个打开的延时器
                if(timeout) clearTimeout(timeout);
                // 停止触发,只有最后一个延时器被保留
                timeout = setTimeout(function(){
                    timeout = null;
                    // func绑定this和事件对象event,还差一个函数返回值
                    func.apply(context,args);
                },wait);
            }
        }
        function wirteCount(){
            this.innerHTML = count++;
        }
        // debounce被执行必须返回一个函数
        app.onmousemove = debounce(wirteCount,1000);
    </script>
    

    二版:增加函数返回值
    如果需要防抖的函数 wirteCount 函数有返回值我们也应该予以保留。

    function wirteCount(){
        this.innerHTML = count++;
        return "我需要返回一些东西";
    }
    

    二版实现,就增加了一个 result 变量来接收 wirteCount 函数返回值:

    <script>
        let count = 0;
        // debounce是一个高阶函数
        function debounce(func,wait){
            let timeout,context,result;
            return function(...args){
                // 这个函数里面的this就是要防抖函数要的this
                //args就是事件对象event
                context = this;
    
                // 一直触发一直清除上一个打开的延时器
                if(timeout) clearTimeout(timeout);
                // 停止触发,只有最后一个延时器被保留
                timeout = setTimeout(function(){
                    timeout = null;
                    // func绑定this和事件对象event,还差一个函数返回值
                    result = func.apply(context,args);
                },wait);
    
                return result;
            }
        }
        function wirteCount(){
            this.innerHTML = count++;
            return "我需要返回一些东西";
        }
        // debounce被执行必须返回一个函数
        app.onmousemove = debounce(wirteCount,1000);
    </script>
    

    最最难的点来了,debounce 第三个参数的实现,定义防抖函数刚触发就执行,还是触发之后等 wait 秒在执行。


    三版:

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>debounce and throttle</title>
        <style>
            section{
                margin: 50px auto;
                width: 900px;
                height: 300px;
            }
            div{
                height: 300px;
                width: 400px;
                float: left;
                display: flex;
                justify-content: center;
                align-items: center;
                color: #fff;
                margin-left: 50px;
                font-size: 38px;
                background-color: #222;
            }
    
        </style>
    </head>
    <body>
        <section>
            <div id="app"></div>
            <div id="box"></div>
        </section>
        
    <script>
        let count = 0;
        let idx = 0;
        // debounce是一个高阶函数
        function debounce(func,wait,immediate){
            let timeout,context,result;
            return function(...args){
                // 这个函数里面的this就是要防抖函数要的this
                //args就是事件对象event
                context = this;
    
                // 一直触发一直清除上一个打开的延时器
                if(timeout) clearTimeout(timeout);
    
                if(immediate){
                    // 第一次触发,timeout===undefined恰好可以利用timeout的值
                    const callNow = !timeout;
                    timeout = setTimeout(function(){
                        timeout = null;
                    },wait);
                    if(callNow) result = func.apply(context,args);
    
                }else{
                    // 停止触发,只有最后一个延时器被保留
                    timeout = setTimeout(function(){
                        timeout = null;
                        // func绑定this和事件对象event,还差一个函数返回值
                        result = func.apply(context,args);
                    },wait);
                }
                
    
                return result;
            }
        }
        function wirteCount(){
            if(this.id === "box"){
                this.innerHTML = count++;
            }else{
                this.innerHTML = idx++;
            }
            return "我需要返回一些东西";
        }
        // debounce被执行必须返回一个函数
        app.onmousemove = debounce(wirteCount,500,false);
        box.onmousemove = debounce(wirteCount,500,true);
    </script>
    </body>
    </html>
    

    四版:现在就差一个取消操作了,取消操作我们需要做些改变,需要把 debounce 函数返回的函数提取出来进行扩展。


    2S内可以取消事件执行
    <!DOCTYPE html>
    <html lang="en">
    
    <head>
        <meta charset="UTF-8">
        <title>debounce and throttle</title>
        <style>
        div {
            height: 300px;
            width: 400px;
            margin: 50px auto;
            display: flex;
            justify-content: center;
            align-items: center;
            color: #fff;
            font-size: 38px;
            background-color: #222;
        }
        button{
            display: block;
            height: 30px;
            width: 60px;
            margin: 0 auto;
        }
        </style>
    </head>
    
    <body>
        <div id="app"></div>
        <button id="btn">取消</button>
        <script>
        let count = 0;
        let idx = 0;
        // debounce是一个高阶函数
        function debounce(func, wait, immediate) {
            let timeout, context, result;
    
            function resDebounced(...args) {
                // 这个函数里面的this就是要防抖函数要的this
                //args就是事件对象event
                context = this;
    
                // 一直触发一直清除上一个打开的延时器
                if (timeout) clearTimeout(timeout);
    
                if (immediate) {
                    // 第一次触发,timeout===undefined恰好可以利用timeout的值
                    const callNow = !timeout;
                    timeout = setTimeout(function() {
                        timeout = null;
                    }, wait);
                    if (callNow) result = func.apply(context, args);
    
                } else {
                    // 停止触发,只有最后一个延时器被保留
                    timeout = setTimeout(function() {
                        timeout = null;
                        // func绑定this和事件对象event,还差一个函数返回值
                        result = func.apply(context, args);
                    }, wait);
                }
                return result;
            }
            resDebounced.cancal = function(){
                clearTimeout(timeout);
                timeout = null;
            }
            return resDebounced;
        }
    
        function wirteCount() {
            this.innerHTML = count++;
            return "我需要返回一些东西";
        }
    
        const implement = debounce(wirteCount, 2000, false);
    
        // debounce被执行必须返回一个函数
        app.onmousemove = implement;
    
        // 取消防抖
        btn.onclick = implement.cancal;
        </script>
    </body>
    
    </html>
    

    debounce 到此就写完了,到此你能看懂几乎所有第三方源码实现了,因为它们的实现基本都大同小异。

    三、throttle 实现

    你一定认为 debounce 都实现了,throttle 就不是很难了,No no 。throttle 最难的是第三个参数的实现思路,先来看看 underscore 中 throttle 的用法,throttle 前两个参数和 debounce 没啥区别,区别在于第三个参数不是 boolean 值,而是一个对象_throttle(func,wait,{leading: true,trailing:true}) leading 表示事件触发立即执行 func ,trailing 表示最后离开是否触发 func。两个都默认为 true。

    前置知识:
    debounce 函数一样,也有三个难点:

    • 需要防抖函数中的 this,通过 apply 绑定;
    • 需要防抖函数的事件对象 event ,通过 apply 传入;
    • 需要防抖函数返回结果不能改变

    现在这个就比较简单了,通过这三个变量 let ctx, args, result; 完美接受实现,下面主要关注实现 throttle 第三个参数的实现。

    3.1 leading 实现

    leading :函数一触发就立即执行 func ,然后稳定的间隔执行 func ,最后一次离开不执行 func。

    下面通过时间戳来实现的:

    1. 刚开始 old = 0 条件 now - old > wait 一定为真,也就是 func 立即触发。
    2. now - old > wait 第一次为真之后,func 就能稳定执行。
    3. 最后离开不会执行 func ,快速进入快速离开,你会发现 func 只在进入执行了一次。
    <!DOCTYPE html>
    <html lang="en">
    
    <head>
        <meta charset="UTF-8">
        <title>debounce and throttle</title>
        <style>
        div {
            height: 300px;
            width: 400px;
            margin: 50px auto;
            display: flex;
            justify-content: center;
            align-items: center;
            color: #fff;
            font-size: 38px;
            background-color: #222;
        }
        button{
            display: block;
            height: 30px;
            width: 60px;
            margin: 0 auto;
        }
        </style>
    </head>
    
    <body>
        <div id="app"></div>
        <button id="btn">取消</button>
        <script>
        let count = 0;
        let idx = 0;
        // throttle是一个高阶函数
        function throttle(func,wait){
            let ctx,args,result;
            let old = 0;
            return function(){
                ctx = this;
                args = arguments;
                let now = Date.now();
                if(now - old > wait){
                    result = func.apply(ctx,args);
                    old = now;
                };
    
                return result;
            }
        }
    
        function wirteCount() {
            this.innerHTML = count++;
            return "我需要返回一些东西";
        }
    
        const implement = throttle(wirteCount, 1000);
    
        // throttle被执行必须返回一个函数
        app.onmousemove = implement;
    
        </script>
    </body>
    
    </html>
    

    3.2 trailing 的实现

    • 第一次进入不触发,然后稳定的间隔执行 func ,最后一次离开执行 func。
    • 快速进入快速离开,你会发现 func 只在离开后执行了一次。
    <!DOCTYPE html>
    <html lang="en">
    
    <head>
        <meta charset="UTF-8">
        <title>debounce and throttle</title>
        <style>
        div {
            height: 300px;
            width: 400px;
            margin: 50px auto;
            display: flex;
            justify-content: center;
            align-items: center;
            color: #fff;
            font-size: 38px;
            background-color: #222;
        }
        button{
            display: block;
            height: 30px;
            width: 60px;
            margin: 0 auto;
        }
        </style>
    </head>
    
    <body>
        <div id="app"></div>
        <button id="btn">取消</button>
        <script>
        let count = 0;
        let idx = 0;
        function throttle(func,wait){
            let ctx,args,result,timeout;
            return function(){
                ctx = this;
                args = arguments;
                if(!timeout){
                    timeout = setTimeout(function(){
                        timeout = null;
                        result = func.apply(ctx,args);
                    },wait);
                };
                return result;
            }
        }
    
        function wirteCount() {
            this.innerHTML = count++;
            return "我需要返回一些东西";
        }
    
        const implement = throttle(wirteCount, 1000);
    
        // throttle被执行必须返回一个函数
        app.onmousemove = implement;
    
        </script>
    </body>
    
    </html>
    

    3.3 leading 和 trailing 二合一实现 throttle

    <!DOCTYPE html>
    <html lang="en">
    
    <head>
        <meta charset="UTF-8">
        <title>debounce and throttle</title>
        <style>
        div {
            height: 300px;
            width: 400px;
            margin: 50px auto;
            display: flex;
            justify-content: center;
            align-items: center;
            color: #fff;
            font-size: 38px;
            background-color: #222;
        }
        button{
            display: block;
            height: 30px;
            width: 60px;
            margin: 0 auto;
        }
        </style>
    </head>
    
    <body>
        <div id="app"></div>
        <button id="btn">取消</button>
        <script>
        let count = 0;
        let idx = 0;
        function throttle(func,wait,options = {}){
            let ctx, args, result, timeout, old = 0;
            let later = function(){
                result = func.apply(ctx,args);
                // 只要执行func,old时间戳就的重置
                old = Date.now();
                timeout = null;
            }
    
            function resThrottle(){
                ctx = this;
                args = arguments;
                let now = Date.now();
    
                // 第一次触发函数是否执行
                if(options.leading === false && !old){
                    old = now;
                }
                if(now - old > wait){
                    // 当条件now - old > wait为假时,会开启延时器
                    // 所以我们要清除下
                    if(timeout){
                        clearTimeout(timeout);
                        timeout = null;
                    }
                    result = func.apply(ctx,args);
                    old = now;
                }else if(!timeout && options.trailing !== false){
                    timeout = setTimeout(later,wait);
                };
    
                return result;
            }
    
            resThrottle.cancal = function(){
                clearTimeout(timeout);
                old = 0;
                timeout = context = args = null;
            };
    
            return resThrottle;
        }
    
        function wirteCount() {
            this.innerHTML = count++;
            return "我需要返回一些东西";
        }
    
        const implement = throttle(wirteCount, 5000);
    
        // throttle被执行必须返回一个函数
        app.onmousemove = implement;
    
        // 
        btn.onclick = implement.cancal;
        </script>
    </body>
    
    </html>
    

    写作于北京昌平区 当前时间 Saturday, June 27, 2020 02:29:33

    相关文章

      网友评论

        本文标题:debounce and throttle

        本文链接:https://www.haomeiwen.com/subject/htbufktx.html