【跟着大佬学JavaScript】之节流

流浪地球
• 阅读 706

前言

js的典型的场景

  • 监听页面的scroll事件
  • 拖拽事件
  • 监听鼠标的 mousemove 事件
    ...

这些事件会频繁触发会影响性能,如果使用节流,降低频次,保留了用户体验,又提升了执行速度,节省资源。

原理

节流的原理:持续触发某事件,每隔一段时间,只执行一次。

通俗点说,3 秒内多次调用函数,但是在 3 秒间隔内只执行一次,第一次执行后 3 秒 无视后面所有的函数调用请求,也不会延长时间间隔。3 秒间隔结束后则开始执行新的函数调用请求,然后在这新的 3 秒内依旧无视后面所有的函数调用请求,以此类推。

简单来说:每隔单位时间( 3 秒),只执行一次。

实现方式

目前比较主流的实现方式有两种:时间戳、定时器。

时间戳实现

使用时间戳实现:首先初始化执行事件的时间previous为0,然后将当前的时间戳减去上次执行时间(now - previous),如果大于wait,则直接执行函数,并且将此时的执行时间now赋给previous(previous = now)。

由于首次previous = 0,则此时函数第一次触发就会立即执行。

后续则每隔wait时间执行一次,如果停止触发,则不会再执行函数。

// 由于一开始now - 0 > wait,则这个写法,时间会立即执行,没过一秒会执行一次,停止触发,则不会再执行事件
function throttle(func, wait = 500) {
    let context, now;
    let previous = 0; // 设置过去的执行时间初始值为0
    return function (...args) {
        context = this;
        now = +(Date.now() || new Date().getTime());
        if (now - previous > wait) {
            func.apply(context, args);
            previous = now;
        }
    };
}

定时器实现

使用定时器实现:首先初始化timeout,然后定义!timeout为true的情况下,直接执行setTimeout,,等待wait时间后执行函数,然后清空timeout,以此类推,重新进入也会按上述执行。

由于进入函数,就执行setTimeout,所以不会立即触发函数执行。

后续则每隔wait时间执行一次,如果停止触发,而后还会触发执行一次函数。

// 由于一进入就创建了定时器,所以不会立即触发函数执行
function throttle(func, wait = 500) {
    let context, timeout;
    
    return function (...args) {
        context = this;
        
        if (!timeout) {
            timeout = setTimeout(function () {
                timeout = null;
                func.apply(context, args);
            }, wait);
        }
    };
}

合并版本

如果,我们需要既刚开始就立即执行,停止触发后,还会触发执行一次函数。

下面,我们将定时器和时间戳合并,组成一个全新的节流版本。

function throttle(func, wait = 500) {
    let context, timeout, result;
    let previous = 0;
    const throttled = function (...args) {
        context = this;
        const now = +(Date.now() || new Date().getTime()); // 当前时间
        // 下次触发 func 剩余时间
        const remaining = wait - (now - previous);
        
        // 如果没有剩余时间或者改了系统时间,这时候不需要等待,直接立即执行,这样就会第一次就执行
        if (remaining <= 0 || remaining > wait) {
            if (timeout) {
                clearTimeout(timeout);
                timeout = null;
            }
            previous = now;
            func.apply(context, args);
        } else if (!timeout) {
            // 剩余的情况就是remaining<=wait的情况,这里使用setTimeout就可以最后也会执行一次
            timeout = setTimeout(function () {
                timeout = null;
                previous = +(Date.now() || new Date().getTime()); // 这里是将previous重新赋值当前时间
                func.apply(context, args);
            }, remaining);
        }
    };
    return throttled;
}

合并版本优化

由于合并后的版本并没用返回值的优化+取消功能。

下面对代码进行返回值+取消功能优化:

function throttle(func, wait = 500) {
    let context, timeout, result;
    let previous = 0;
    
    const showResult = function (e1, e2) {
        result = func.apply(e1, e2);
        return result;
    };
    
    const throttled = function (...args) {
        context = this;
        const now = +(Date.now() || new Date().getTime()); // 当前时间
        // 下次触发 func 剩余时间
        const remaining = wait - (now - previous);
        
        // 如果没有剩余时间或者改了系统时间,这时候不需要等待,直接立即执行,这样就会第一次就执行
        if (remaining <= 0 || remaining > wait) {
            if (timeout) {
                clearTimeout(timeout);
                timeout = null;
            }
            previous = now;
            return showResult(context, args);
        } else if (!timeout) {
            // 剩余的情况就是remaining<=wait的情况,这里使用setTimeout就可以最后也会执行一次
            timeout = setTimeout(function () {
                timeout = null;
                previous = +(Date.now() || new Date().getTime()); // 这里是将previous重新赋值当前时间
                return showResult(context, args);
            }, remaining);
        }
        retrun result
    };
    
    throttled.cancel = function () {
        if (timeout !== undefined) {
            clearTimeout(timeout);
        }
        previous = 0;
        context = timeout = result = undefined;
    };
    return throttled;
}

功能性优化

有时候,我们也希望无头有尾,或者有头无尾。

function throttle(func, wait = 500, options = {}) {
    let context, timeout, result;
    let previous = 0;
    
   // 如果同时设置无头无尾,则直接使用默认设置,其他情况,则走下述操作
    if (!(options.leading === false && options.trailing === false)) {
        leading = !!options.leading; // 默认去除立即执行部分
        trailing = "trailing" in options ? !!options.trailing : true; // 默认保留尾部
    }
    
    // 返回原函数的return
    const showResult = function (e1, e2) {
        result = func.apply(e1, e2);
        return result;
    };
    
    // 获取当前时间
    const getNow = function () {
        return +(Date.now() || new Date().getTime());
    };
    
    const throttled = function (...args) {
        context = this;
        const now = getNow(); // 当前时间
        // 下次触发 func 剩余时间
        if (!previous && leading === false) previous = now;
        const remaining = wait - (now - previous);
        
        // 如果没有剩余时间或者改了系统时间,这时候不需要等待,直接立即执行,这样就会第一次就执行
        if (remaining <= 0 || remaining > wait) {
            if (timeout) {
                clearTimeout(timeout);
                timeout = null;
            }
            previous = now;
            return showResult(context, args);
        } else if (!timeout && trailing !== false) {
            // 剩余的情况就是remaining<=wait的情况,这里使用setTimeout就可以最后也会执行一次
            timeout = setTimeout(function () {
                timeout = null;
                previous = options.leading === false ? 0 : getNow(); // 这里是将previous重新赋值当前时间
                return showResult(context, args);
            }, remaining);
        }
        return result;
    };
    
    throttled.cancel = function () {
        if (timeout !== undefined) {
            clearTimeout(timeout);
        }
        previous = 0;
        context = timeout = result = undefined;
    };
    return throttled;
}

这里,如果options不传参数,函数默认设置

let leading = false
let trailing = true

也就是无头有尾。

如果同时设置无头无尾,则会直接采用默认设置,无头有尾。

// 如果同时设置无头无尾,则直接使用默认设置,其他情况,则走下述操作
if (!(options.leading === false && options.trailing === false)) {
    leading = !!options.leading; // 默认去除立即执行部分
    trailing = "trailing" in options ? !!options.trailing : true; // 默认保留尾部
}

演示地址

可以去Github仓库查看演示代码

跟着大佬学系列

主要是日常对每个进阶知识点的摸透,跟着大佬一起去深入了解JavaScript的语言艺术。

后续会一直更新,希望各位看官不要吝啬手中的赞。

❤️ 感谢各位的支持!!!

❤️ 如果有错误或者不严谨的地方,请务必给予指正,十分感谢!!!

❤️ 喜欢或者有所启发,欢迎 star!!!

参考

原文地址

【跟着大佬学JavaScript】之节流

点赞
收藏
评论区
推荐文章
blmius blmius
4年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Wesley13 Wesley13
3年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
美凌格栋栋酱 美凌格栋栋酱
7个月前
Oracle 分组与拼接字符串同时使用
SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(
Python进阶者 Python进阶者
3年前
一文解读JavaScript事件对象和表单对象
前言相信做网站对JavaScript再熟悉不过了,它是一门脚本语言,不同于Python的是,它是一门浏览器脚本语言,而Python则是服务器脚本语言,我们不光要会Python,还要会JavaScript,因为它对做网页方面是有很大作用的。1.事件对象(Event)1).事件对象常量bubbles事件是否是起泡事件类型cancelabl
LinMeng LinMeng
4年前
js的防抖与节流
在进行窗口的resize、scroll,输入框内容校验等操作时,如果事件处理函数调用的频率无限制,会加重浏览器的负担,导致用户体验非常糟糕。此时我们可以采用debounce(防抖)和throttle(节流)的方式来减少调用频率,同时又不影响实际效果。函数防抖函数防抖(debounce):当持续触发事件时,一定时间段内没有再触发事件,事件处理函数才
Stella981 Stella981
3年前
Javascript判断Video视频播放、暂停、结束完成及获取长度事件监听处理
在日常应用场景中,可能会遇到这么一个情况,需要判断用户是否完整的观看完了一部视频,在这个场景中,和视频相关的事件大体涉及到几个部分,获取视频长度,视频开始播放,暂停播放和播放结束,下面来看下如何通过JavaScript来监听获取视频的这几种状态。html页面视频标签大体如下:<video id\"video" controls\"controls
Stella981 Stella981
3年前
Apache Synapse 远程代码执行漏洞(CVE
!(https://oscimg.oschina.net/oscnet/435fc4cde65d4aee9a2efca3080cb89e.png)0x00事件背景ApacheSynapse是一个简单、轻量级的高性能企业服务总线(ESB),它是在ApacheSoftwareFoun
Stella981 Stella981
3年前
SpringBoot的事件监听
事件监听的流程分为三步:1、自定义事件,一般是继承ApplicationEvent抽象类。2、定义事件监听器,一般是实现ApplicationListener接口。3、a、启动的时候,需要将监听器加入到Spring容器中。b、或者将监听器加入到容器中。@Componentc、使用@EventLis
Stella981 Stella981
3年前
Noark入门之异步事件
引入异步事件主要是为了各模块的解耦,每当完成一个动作时,向系统发布一个事件,由关心的模块自己监听处理,可选择同步处理,异步处理,延迟处理。何时发布事件,当其他模块关心此动作时<br比如获得道具时,任务系统模块要判定完成进度,BI模块需要上报等等都可以监听此事件,已达模块解耦0x00事件源一个实现xyz.noark.core.event
Stella981 Stella981
3年前
JavaScript 基础(四)
HTMLDOMEvent(事件)HTML4.0的新特性之一是有能力使HTML事件触发浏览器中的动作(action),比如当用户点击某个HTML元素时启动一段JavaScript。下面是一个属性列表,这些属性可插入HTML标签来定义事件动作。onclick//当用户点
菜园前端 菜园前端
2年前
什么是防抖和节流?
原文链接::::tip防抖和节流一般在做用户体验优化的时候会用上。:::什么是防抖?是指一个事件在同一时间内被多次频繁触发后,最终只会执行一次。多次触发后会重新计算时间,只生效最后一次触发。基础案例可通过定时器来实现javascriptvartimer0f