手写Spirit防抖函数underscore和节流函数lodash

目录
  • 前言
  • 防抖函数和节流函数的区别
  • 防抖函数的实现
    • 基本实现
    • 立即执行
    • 取消功能
    • 返回结果
  • 节流函数的实现
    • 基本实现
    • leading实现
    • trailing实现
  • 取消功能和返回结果

前言

防抖函数和节流函数,无论是写业务的时候还是面试的时候,想必大家已经听过很多次了吧.但是大家在用到的时候,有了解过他们之间的区别嘛,他们是如何实现的呢?还是说只是简单的调用下像lodashunderscore这种第三方库提供给我们的节流和防抖函数呢?

防抖函数和节流函数的区别

防抖函数:是指触发了一个事件,在规定的时间内,如果没有第二次事件被触发,那么他就会执行.换句话讲,就是说,如果不断有事件被触发,那么规定的执行时间将会被不断推迟

节流函数:指的是在规定时间内,你无论触发多少次事件,你也只会执行一次.我举个生活中的例子,就很好理解了.王者荣耀这个游戏可能很多人多玩过,每个英雄都有自己的技能,在我们点击一次后,该技能会进入冷却时间,即使我们点的再快,该技能在冷却时间好之前也只能触发一次(我们第一次点击的时候)

防抖函数的实现

我将实现防抖函数的四个功能,希望大家能一步步的跟着来,循序渐进,相信大家一定会有收获的

基本实现

我们可以想下,要想一个事件在规定时间过后执行,在JS中该怎么实现

好 时间到

定时器,小伙伴们肯定都知道的吧

触发事件,在一定时间后执行,这个可以使用定时器解决了.

那么 接下来还有一个问题 在触发事件后,再触发事件,该如何让他推迟执行呢?

如果规定时间内,再触发的话,我们就把之前创建的定时器删除不就好了,对不对?

这样是不是就解决了我们的问题,好,我们现在来写下代码,怕大家有点不明白

function debounce(fn, delay) {
    //定义一个定时器
    let timer = null;
    // 每次触发的时候 清空上一次的定时器
    const _debounce = function () {
        if (timer) clearTimeout(timer);
        //根据传进来的延时 执行
        timer = setTimeout(() => {
            fn();
        }, delay)
    }
    return _debounce;
}

这段代码还是比较容易的吧,相信小伙伴们肯定都懂了

但是这段代码还是有点问题,我们来调用下第三方库的underscore的防抖函数

<body>
    <button>取消</button>
    <input type="text">
    <script src="https://cdn.jsdelivr.net/npm/underscore@1.13.1/underscore-umd-min.js"></script>
    <script>
        const btn = document.querySelector("button");
        const input = document.querySelector("input");
        let count = 0;
        function test(event) {
            // 注意这里的this 和 event
            console.log(`发送${++count}网络请求`, this, event);
            return "我是返回结果";
        }
        input.oninput = _.debounce(test, 2000);
    </script>
</body>

我们打开浏览器调试,看下输出结果

可以看到this和Event输出是没有任何问题的.

再来看看我们的输出

你会发现 this是window了 而Event是undefined.

这是为什么呢?

这是因为 我们写的代码没有对this进行一个绑定,同时也没有将DOM元素的event接收

fn()直接执行 这时候的this是直接指向window的

function debounce(fn, delay) {
    let timer = null;
    //使用剩余参数接收所有的参数 DOM在调用这个函数的时候,我们就能接收到event了
    const _debounce = function (...args) {
        if (timer) clearTimeout(timer);
        timer = setTimeout(() => {
            //注意 我们使用apply进行一个换绑,绑到执行这个的DOM元素上
            fn.apply(this,args);
        }, delay)
    }
    return _debounce;
}

至此,我们这个防抖函数的基本实现就没有任何问题了

看到这里的小伙伴们,你们真不错

这个基本实现 拿来应付面试已经够了,接下来我们还有一些额外的功能要实现,想看的可以继续往下看了,现在不想看的也可以收藏下,以后来看.

立即执行

在某些应用场景下,比如搜索的时候,你输入第一个字符的时候,他就会联想出一系列的字符,他不会等待一段时间后再去执行,而是会立马执行,我们接下来实现下这个功能

首先,立即执行这个功能,我们可以将它交给用户来决定是不是要使用这个功能

debounce(fn,delay,immediate=false)

我们以参数的形式传递,默认是关闭的

好,我们现在来看下代码实现

function debounce(fn, delay, immediate = false) {
    let timer = null;
    //代码规范 我们最好不要修改 用户传递进来的参数
    //所以我们在下面声明了一个变量 用于控制
    let isInvoke = false;
    const _debounce = function (...args) {
        if (timer) clearTimeout(timer);
        //如果immdiate为true
        //isInvoke取反为true
        if (immediate && !isInvoke) {
            //会立马执行一次
            fn.apply(this, args);
            //同时将isInvoke设置为true,防止下次触发的时候 又再次触发立即执行
            isInvoke = true;
        } else {
            //第一次触发结束立即执行后
            //isInvoke会限定在定时器中 输入结束后 才会重新刷新isInvoke
            timer = setTimeout(() => {
                //剩下的操作在规定时间内 会等待定时器结束
                fn.apply(this, args);
                //同时重新刷新inInvoke
                isInvoke = false;
            }, delay)
        }
    }
    return _debounce;
}

好,这一块还是比较简单的吧,相比大家应该看懂了,如果有什么不懂的地方,欢迎评论区留言,我看见了就会回答的

那么我们开始下一个篇章的 如果用户输入之后 不想让他请求呢 这时候我们就需要一个取消功能了,对,我们接下来就是要实现取消功能了

取消功能

我们该如何在剩余的时间内取消请求呢?

对 没错! 清空定时器

我们只需要在我们返回的函数上加个静态方法 给用户提供个取消功能即可

我们来看下代码实现

 // 给返回的这个函数添加一个静态方法 用于取消请求
    _debounce.cancel = function () {
        if (timer) clearTimeout(timer);
    }

是不是很简单呢? 就这么简单一行代码 取消功能就完成了

好,我们还有最后一个功能需要实现的 那就是如果开发者想要拿到请求后的返回结果的话,我们现阶段的防抖函数能不能做到呢? 貌似不行吧?

所以接下来,我们来实现最后一个功能 取到返回结果

返回结果

我们思考一个问题 返回结果在哪呢?

用户传递一个函数给我们 返回一个新的函数

那么返回结果一定是在用户传递给我们的那个函数上的

所以关键就是 将用户的那个函数的返回结果传递出来

现在 我们这里有两个方案

  • 回调函数
  • Promise

我们先来看下回调函数的版本

// 回调函数版本
function debounce(fn, delay, immediate = false, resultCallBack) {
    let timer = null;
    let isInvoke = false;
    let result = null;
    const _debounce = function (...args) {
        if (timer) clearTimeout(timer);
        if (immediate && !isInvoke) {
            //接收结果
            result = fn.apply(this, args);
            resultCallBack(result);
            isInvoke = true;
        } else {
            timer = setTimeout(() => {
                //接收结果
                result = fn.apply(this, args);
                resultCallBack(result);
                isInvoke = false;
            }, delay)
        }
    }
    _debounce.cancel = function () {
        if (timer) clearTimeout(timer);
        timer = null;
        isInvoke = false;
    }
    return _debounce;
}

实际应用

        const _debounce = () => {
             debounce(test, 1000)().then(res => {
                 console.log(res);
             })
         }
         input.oninput = _debounce;

回调函数的是不是比较简单? 我们来看下Promise版本的 在实际应用的时候要注意一些坑

function debounce(fn, delay, immediate = false) {
    let timer = null;
    let isInvoke = false;
    let result = null;
    const _debounce = function (...args) {
        //在返回的函数中 直接整体返回一个Promsie对象
        //将结果传入 resolve中
        return new Promise((resolve, rejected) => {
            if (timer) clearTimeout(timer);
            if (immediate && !isInvoke) {
                result = fn.apply(this, args);
                resolve(result)
                isInvoke = true;
            } else {
                timer = setTimeout(() => {
                    result = fn.apply(this, args);
                    resolve(result);
                    isInvoke = false;
                }, delay)
            }
        })
    }
    _debounce.cancel = function () {
        if (timer) clearTimeout(timer);
        timer = null;
        isInvoke = false;
    }
    return _debounce;
}

实际调用

  const _debounce = function(...args){
            debounce(test, 1000).apply(this,args).then(res => {
                console.log(res);
            })
        };
  input.oninput = _debounce;

注意到了吧 我们对原来的函数又封装了一层 因为只有这样才能拿到promise的结果

同时this和event不会出问题

看到这里的小伙伴们真棒,相信你们防抖函数一定没问题了,待会我们就开始讲解 节流函数该如何实现

节流函数的实现

节流函数我们也是从几个方面逐步实现,带着大家一步步的解开节流函数的面纱.

基本实现

大家可以想下,节流函数该如何实现.

一段时间内,只会触发一次操作,后续的操作就不会被触发.

我们可以拿到当前的时间戳 来进行计算

我们直接通过代码来讲吧 比较方便讲

function throttle(fn, interval) {
    let lastTime = 0;
    const _throttle = function () {
        //首先拿到当前的时间
        const nowTime = new Date().getTime();
        //传递进来的时间间隔 用当前的时间减去上一次触发的时间
        //得到最新的剩余时间
        const reamainTime = interval - (nowTime - lastTime);
        if (reamainTime <= 0) {
            fn();
            //如果剩余时间小于0 说明已经达到一个间隔
            //并且将现在的时间赋值给lastTime
            //在时间间隔内 这样无论执行多少次 都只会执行第一次的操作
            //因为第一次的lastTime是0 而nowTime是比较大的
            //减去之后一定是个负数 所以会执行第一次
            //而不会执行后续的操作
            lastTime = nowTime;
        }
    }
    return _throttle;
}

大家看下我上面这段代码 还是比较好理解的吧,面试的话能够写出这一部分已经很可以了,但是要想更加出彩,能和面试官多唠会的吧,我们接着看下面的实现

leading实现

我们在基本实现中,其实已经将这个功能已经实现了,但是并不是可以控制的,我们这个实现是将是否首次触发交给用户来决定,大家可以想下该如何实现

基本实现中,我们是如何实现第一次触发的?

是不是通过拿到的时间戳非常大,而lastTime为0所导致的呢?

所以我们是不是可以让lastTime也能拿到当前的时间戳呢,这样子, nowTime和lastTime相减的时候,是不是就不会变成负数呢?

代码实现

// 考虑到 我们后面会有很多功能要实现
//所以我们使用选项来进行配置.避免造成更多参数
function throttle(fn, interval, option = { leading: true }) {
    let lastTime = 0;
    const { leading } = option;
    const _throttle = function () {
        const nowTime = new Date().getTime();
        //在 leading和lastTime为false的情况下
        //就将nowTime赋值给lastTime,这样就不会在第一次就执行操作了
        if (!leading && !lastTime) lastTime = nowTime;
        const reamainTime = interval - (nowTime - lastTime);
        if (reamainTime <= 0) {
            fn();
            lastTime = nowTime;
        }
    }
    return _throttle;
}

大家是不是理解了呢? 我个人认为还是比较好懂的吧,不懂的可以在评论区留言,我看到就会给大家解答的

接下来,我们看下和这个情况相反的一种状况,如果我们想要在最后一次操作的时候进行一次触发操作的话,该如何去做呢?

trailing实现

这一块是比较难的部分了,会有点难,大家不懂的话,要多看几遍,实在有不明白的,欢迎评论区留言

首先最后一次触发操作时,我们该怎么样让它执行?

我提供一个思路,当我们最后一次触发操作的时候,拿到距离间隔还有多少时间结束,加上一个定时器,让他根据这个剩余时间去按时执行

代码实现

function throttle(fn, interval, option = { leading: true, tralling: false }) {
    let lastTime = 0;
    let timer = null;
    const { leading, tralling } = option;
    const _throttle = function (...args) {
        const nowTime = new Date().getTime();
        if (!leading && !lastTime) lastTime = nowTime;
        const reamainTime = interval - (nowTime - lastTime);
        if (reamainTime <= 0) {
            fn.apply(this, args);
            lastTime = nowTime;
            if (timer) {
                clearTimeout(timer)
                timer = null;
            }
            // 如果执行了这一部分 那么后面的tralling就没有必要去执行
            // 说明刚好执行到了这一步 后面的最后按下 就不需要
            return;
        }
        if (tralling && !timer) {
            timer = setTimeout(() => {
                timer = null;
                /**     `
                 *  首先 按下第一次的时候 这个定时器已经被加上了
                 *  每次进来的时候 等待一定时间 定时器会被置空 方便下次使用
                 *  根据剩余时间 来判断执行
                 *  如果leading为false lastTime会被设置为0 会在规定的剩余时间到达后 去执行这个函数 而remianTime那个部分就不会被执行 因为remainTime会一直保持在一个正数状态
                 *  如果leading为true lastTime会被设置为当前的时间 这样在下一次的操作下,remainTime才会发生变化
                 *
                 */
                lastTime = !leading ? 0 : new Date().getTime();
                fn.apply(this, args);
            }, reamainTime)
        }
    }
    return _throttle;
}

是不是比较难懂呢? 我在来解释一下啊

首先如果remainTime已经小于0了,那么fn就会去执行,我们也就不需要去执行后续的操作了 会直接返回

那么如果remainTime没有小于0,我们会设置定时器,在定时器内部,我们需要先把timer清空,防止下一次触发的时候又触发了.

其次,我们要将lastTime进行一个处理

如果我们之前设置的leading是false的话,那么我们需要将lastTime置为0,这样在下一次的触发操作的时候,才能触发leading为false的情况下的逻辑语句

leading为true的情况下,需要将lastTime设置为当前的时间戳,这样在下一次的操作的时候,才会remainTime才会发生变化,逻辑才能执行下去.

大家有没有听明白呢? 可能是会有点难懂,但是好好多看几遍,还是能够理解的我相信!!!

接下来的操作就比较简单了,大家可以安心食用,和防抖函数一样,是取消功能和返回结果

取消功能和返回结果

因为这个和防抖函数是一样的,所以我这里直接就放代码了

function throttle(fn, interval, option = { leading: true, tralling: false, resultCallback }) {
    let lastTime = 0;
    let timer = null;
    let result = null;
    const { leading, tralling, resultCallback } = option;

    // 两种结果回调
    //和防抖函数是一样的
    //1. 通过传递一个回调函数
    //2. 通过promise 进行结果回调
    const _throttle = function (...args) {
        return new Promise((resolve, reject) => {
            const nowTime = new Date().getTime();
            if (!leading && !lastTime) lastTime = nowTime;
            const reamainTime = interval - (nowTime - lastTime);
            if (reamainTime <= 0) {
                result = fn.apply(this, args);
                resultCallback(result);
                resolve(result);
                lastTime = nowTime;
                if (timer) {
                    clearTimeout(timer)
                    timer = null;
                }
                return;
            }
            if (tralling && !timer) {
                timer = setTimeout(() => {
                    timer = null;
                    lastTime = !leading ? 0 : new Date().getTime();
                    result = fn.apply(this, args);
                    resultCallback(result);
                    resolve(result);
                }, reamainTime)
            }
        })
    }
    //取消功能
    _throttle.cancel = function () {
        if (timer) clearTimeout(timer);
        timer = null;
        lastTime = 0;
    }
    return _throttle;
}

大家可以看下是不是一摸一样的呢? 非常轻松吧

以上就是手写Spirit防抖函数underscore和节流函数lodash的详细内容,更多关于pirit防抖函数underscore节流函数lodash的资料请关注我们其它相关文章!

(0)

相关推荐

  • JavaScript函数节流和函数防抖之间的区别

    一.概念解释 函数节流和函数防抖,两者都是优化高频率执行js代码的一种手段. 大家大概都知道旧款电视机的工作原理,就是一行行得扫描出色彩到屏幕上,然后组成一张张图片.由于肉眼只能分辨出一定频率的变化,当高频率的扫描,人类是感觉不出来的.反而形成一种视觉效果,就是一张图.就像高速旋转的风扇,你看不到扇叶,只看到了一个圆一样. 同理,可以类推到js代码.在一定时间内,代码执行的次数不一定要非常多.达到一定频率就足够了.因为跑得越多,带来的效果也是一样.倒不如,把js代码的执行次数控制在合理的范围.既

  • 一文看懂如何简单实现节流函数和防抖函数

    前言 在日常开发中有很多场景我们都需要用到节流函数和防抖函数,比如:实现输入框的模糊查询因为需要轮询ajax,影响浏览器性能,所以需要用到节流函数:实现手机号.姓名之类的的验证,往往我们只需要验证一次,这个时候我们就需要用到防抖函数:但是网上的很多资料都是不够具体和便于理解.今天自己翻阅了一些资料之后,来简单的谈谈我对节流函数和防抖函数的理解,希望能帮助大家理解: 节流函数 顾名思义,就是节省流量节省内存性能的一种函数,可以理解为是一种性能优化方案: 举个例子:一个水龙头一直在滴水,可能一次性会

  • Javascript节流函数throttle和防抖函数debounce

    问题的引出 在一些场景往往由于事件频繁被触发,因而频繁地进行DOM操作.资源加载,导致UI停顿甚至浏览器崩溃. 在这样的情况下,我们实际上的需求大多为停止改变大小n毫秒后执行后续处理:而其他事件大多的需求是以一定的频率执行后续处理.针对这两种需求就出现了debounce和throttle两种解决办法. 1. resize事件 2. mousemove事件 3. touchmove事件 4.scroll事件 throttle 与 debounce 在现在很多的javascript框架中都提供了这两

  • JS函数节流和函数防抖问题分析

    问题1:如果实现了dom拖拽功能,但是在绑定拖拽事件的时候发现每当元素稍微移动一点便触发了大量的回调函数,导致浏览器直接卡死,这个时候怎么办? **问题2:**如果给一个按钮绑定了表单提交的post事件,但是用户有些时候在网络情况极差的情况下多次点击按钮造成表单重复提交,如何防止多次提交的发生? 为了应对如上场景,便出现了 函数防抖 和 函数节流 两个概念,总的来说: 这两个方法是在 时间轴上控制函数的执行次数. 函数防抖(debounce) 概念: 在事件被触发n秒后再执行回调,如果在这n秒内

  • JS函数节流和防抖之间的区分和实现详解

    在写JS时,这两个函数比较常见,有时候傻傻分不清用哪个,或者说知道代码要怎么写,但要说出它究竟是节流函数还是防抖函数时一脸楞逼.今天有一个同学分享了这两个的区分,我也来回顾一下,加深一下印象,以便日后用到时心里有底.PS:百度和谷歌搜索前几个介绍都是相反介绍,本文为原创,如有雷同纯属抄袭我的. 节流概念(Throttle) 按照设定的时间固定执行一次函数,比如200ms一次.注意:固定就是你在mousemove过程中,执行这个节流函数,它一定是200ms(你设定的定时器延迟时间)内执行一次.没到

  • 手写Spirit防抖函数underscore和节流函数lodash

    目录 前言 防抖函数和节流函数的区别 防抖函数的实现 基本实现 立即执行 取消功能 返回结果 节流函数的实现 基本实现 leading实现 trailing实现 取消功能和返回结果 前言 防抖函数和节流函数,无论是写业务的时候还是面试的时候,想必大家已经听过很多次了吧.但是大家在用到的时候,有了解过他们之间的区别嘛,他们是如何实现的呢?还是说只是简单的调用下像lodash和underscore这种第三方库提供给我们的节流和防抖函数呢? 防抖函数和节流函数的区别 防抖函数:是指触发了一个事件,在规

  • Python-OpenCV实战:利用 KNN 算法识别手写数字

    目录 前言 手写数字数据集 MNIST 介绍 基准模型--利用 KNN 算法识别手写数字 改进模型1--参数 K 对识别手写数字精确度的影响 改进模型2--训练数据量对识别手写数字精确度的影响 改进模型3--预处理对识别手写数字精确度的影响 改进模型4--使用高级描述符作为图像特征提高 KNN 算法准确率 完整代码 相关链接 前言 K-最近邻 (k-nearest neighbours, KNN) 是监督学习中最简单的算法之一,KNN 可用于分类和回归问题,在博文<Python OpenCV实战

  • 如何在面试中手写出javascript节流和防抖函数

    面试的时候我们经常会问别人是理解什么是节流和防抖,严格的可能要求你写出节流和防抖函数,这里我们抛开loadsh工具库手写节流和防抖 1.节流函数throttle // 节流方案1,每delay的时间执行一次,通过开关控制 function throttle(fn, delay, ctx) { let isAvail = true return function () { let args = arguments // 开关打开时,执行任务 if (isAvail) { fn.apply(ctx,

  • JS防抖节流函数的实现与使用场景

    目录 一.什么是函数防抖 1.为什么需要函数防抖? 2.函数防抖的要点 3.函数防抖的实现 4.函数防抖的使用场景 二.什么是函数节流 1.函数节流的要点 2.函数节流的实现 3.函数节流的使用场景 总结 一.什么是函数防抖 概念:函数防抖(debounce),就是指触发事件后,在 n 秒内函数只能执行一次,如果触发事件后在 n 秒内又触发了事件,则会重新计算函数延执行时间. 1.为什么需要函数防抖? 前端开发过程中,有一些事件,常见的例如,onresize,scroll,mousemove ,

  • 简单实现节流函数和防抖函数过程解析

    在日常开发中有很多场景我们都需要用到节流函数和防抖函数,比如:实现输入框的模糊查询因为需要轮询ajax,影响浏览器性能,所以需要用到节流函数:实现手机号.姓名之类的的验证,往往我们只需要验证一次,这个时候我们就需要用到防抖函数:但是网上的很多资料都是不够具体和便于理解.今天自己翻阅了一些资料之后,来简单的谈谈我对节流函数和防抖函数的理解,希望能帮助大家理解: 节流函数 顾名思义,就是节省流量节省内存性能的一种函数,可以理解为是一种性能优化方案: 举个例子:一个水龙头一直在滴水,可能一次性会滴很多

  • 一些手写JavaScript常用的函数汇总

    前言 在JavaScript中,几乎每次编写一段代码时,通常都会写入一个函数.我们的大部分代码执行都是函数调用的结果.所以本文主要给大家介绍了一些JavaScript常用的函数,下面话不多说了,来一起看看详细的介绍吧 JavaScript常用的函数 一.bind.call.apply函数的实现改变函数的执行上下文中的this指向,但不执行该函数(位于Function构造函数的原型对象上的方法) Function.prototype.myBind = function (target) { if

  • js防抖函数和节流函数使用场景和实现区别示例分析

    本文实例讲述了js防抖函数和节流函数使用场景和实现区别.分享给大家供大家参考,具体如下: 开发过程中,都遇到过某个事件被频发触发的场景,比如resize,scroll事件,input事件,而对应的事件处理函数也会被高频率调用,这时会增加浏览器负担,用户体验也不好,这也是防抖函数和节流函数存在的意义和使用场景. 函数防抖(debounce): 持续触发事件时,在设定时间段内没有被触发,才去调用事件处理函数,在设定时间段内如果事件又被触发,则不调用事件处理函数,并从触发事件时间重新开始延时. 具体实

  • JavaScript手写数组的常用函数总结

    前言 在开发过程中,我们常常使用数组的一些 api 相关操作,其中包含 forEach . filter . find . findIndex . map . some . every . reduce . reduceRight 等函数方法. 今天,我们试试手写这些函数,实现数组这些函数方法.为了方便,直接在数组原型对象 prototype 上扩展. 本文 Githab 已上传,更多往期文章已分类整理. 正文 参数说明 callbackFn 回调函数 thisArg 执行 callbackFn

  • javascript的防抖节流函数解析

    目录 防抖节流函数的解析 认识防抖和节流函数 认识防抖debounce函数 防抖函数的案例 认识节流throttle函数 节流函数的应用场景 自定义防抖和节流函数 总结 防抖节流函数的解析 认识防抖和节流函数 防抖和节流的概念其实最早并不是出现在软件工程中,防抖是出现在电子元件中,节流出现在流体流动中 而JavaScript是事件驱动的,大量的操作会触发事件,加入到事件队列中处理. 而对于某些频繁的事件处理会造成性能的损耗,我们就可以通过防抖和节流来限制事件频繁的发生: 防抖和节流函数目前已经是

随机推荐