专栏首页前端与Java学习JavaScript网页性能优化(函数防抖与函数节流)
原创

JavaScript网页性能优化(函数防抖与函数节流)

01-函数防抖

1.什么是函数防抖? (debounce)

  • 网上主流解释:函数防抖,就是指触发事件后在 n 秒内函数只能执行一次,如果在 n 秒内又触发了事件,则会重新计算函数执行时间。
  • 参考博客:https://www.jianshu.com/p/f9f6b637fd6c
  • 参考博客:https://segmentfault.com/a/1190000018445196
  • 笔者解释
    • 先理解什么是抖动?:例如用户鼠标轻微晃动,快速划过某一个元素(用户本身不想触发,只是鼠标误触发,常见于鼠标事件 移入/移出/移动 )。 例如输入框手机和邮箱验证,用户在不停的输入,还没有输完的时候其实是不需要验证的,应该等用户输入完毕后再验证。
    • 防抖 :如果用户鼠标轻微晃动,在某一个元素上停留时间很短,则认为是用户误触发,则不执行本次事件处理函数
      • 一句话总结:用户连续多次触发某个事件,则只执行最后一次
    • 由于函数防抖 属于 前端中的 网页性能优化技术,因此初学者刚开始学习会有一些吃力,并且很多网站都没有做防抖处理(性能优化)

没有做函数防抖处理的用户体验如下

  • 假设当前鼠标在第一张,此时用户想看第五张。正常情况下,鼠标会依次触发 第二、第三、第四张的移入事件,但这不是用户真正想要触发的(误触发)

有做函数防抖处理的用户体验如下

  • 用户从第一张 滑动到第五张,由于鼠标在 第二、第三、第四张停留时间很短(假设小于0.5秒),所以判定为用户误触发,则不触发对应的事件处理函数

2.函数防抖解决思路

  • ==使用定时器:保证用户多次触发事件时,以最后一次触发为准==
  • 1.每一次移入元素时 : 不立即触发该事件处理函数,而是开启定时器,间隔0.5s(防抖间隔)之后再执行事件处理函数。
    • 此时并没有彻底解决函数防抖,因为用户多次触发事件时,每一个定时器都会在0.5s之后,依次执行
  • 2.每一次移入元素时 : 先清除上一次的定时器
    • 保证用户多次触发事件时,以最后一次触发为准
  • 注意点:定时器中的this默认为window,需要使用上下文模式bind()动态修改为当前事件源
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <title>动画-案例《手风琴》</title>
    <style>
        * {
            margin: 0;
            padding: 0;
        }
        ul {
            list-style: none;
            width: 2400px;
        }
        #box {
            width: 1200px;
            height: 400px;
            border: 2px solid red;
            margin: 100px auto;
            overflow: hidden;
        }
        #box li {
            width: 240px;
            height: 400px;
            float: left;
        }
    </style>
</head>

<body>
    <div id="box">
        <ul>
            <li><img src="./images/collapse/1.jpg" alt=""></li>
            <li><img src="./images/collapse/2.jpg" alt=""></li>
            <li><img src="./images/collapse/3.jpg" alt=""></li>
            <li><img src="./images/collapse/4.jpg" alt=""></li>
            <li><img src="./images/collapse/5.jpg" alt=""></li>
        </ul>
    </div>

    <script src="./animation.js"></script>
    <script>

        /* 
        1. 鼠标抖动 : 例如用户鼠标轻微晃动,快速划过某一个元素(用户本身不想触发,只是鼠标误触发,常见于鼠标事件 移入/移出/移动 )

        2. 函数防抖 : 用户连续多次触发某个事件,则只执行最后一次
            * 如果用户鼠标轻微晃动,在某一个元素上停留时间很短,则认为是用户误触发,则不执行本次事件处理函数
            * 影响 : 会触发一些本没有必要触发的事件,浪费代码性能

        3. 函数防抖解决思路 : 
            3.1 声明全局变量存储定时器ID
            3.2 每一次移入元素时 : 先清除上一次的定时器
                * 以最后一次触发为准
            3.3 开启定时器,间隔时间才会触发事件处理函数
                * 用户连续触发事件的时候,全局变量只会存储最后一次触发的事件定时器(前面的都被清除了,认为这是用户误操作产生抖动)
         */

        /*需求
        (1):给每一个li设置鼠标移入事件:当前li的宽度变成800,其他兄弟li宽度变成100
        (2):鼠标移出大盒子,所有的li的宽度都变成默认的240 
         */

        //1.获取元素
        var liList = document.querySelectorAll('#box li');//li元素列表
        var box = document.querySelector('#box');//li元素列表

        //2.注册事件

        // 函数防抖一 : 声明全局变量存储定时器
        var timeID = null;

        //2.1 鼠标移入事件:当前li的宽度变成800,其他兄弟li宽度变成100
        for (var i = 0; i < liList.length; i++) {
            liList[i].onmouseover = function () {
                //this:事件源

                // 函数防抖二 : 每一次移动新的li元素,移除旧的定时器,以最后一次触发为准
                clearTimeout(timeID);

                //函数防抖三 : 间隔500ms后执行
                /* 注意点:定时器中的this默认是window,需要使用bind修改为事件源 */
                timeID = setTimeout(function () {
                    //3.事件处理函数 (触发事件)
                    console.log(1111);
                    //3.排他思想修改样式
                    for (var j = 0; j < liList.length; j++) {
                        if (liList[j] == this) {//是自己
                            animationSlow(liList[j], { width: 800 });
                        } else {
                            animationSlow(liList[j], { width: 100 });
                        }
                    };
                }.bind(this), 500);
            };
        };

        //2.2 鼠标移出大盒子,所有的li的宽度都变成默认的240 
        /* 这个事件目前对我们的函数防抖会有影响 : 事件冒泡 */
        /* jq第一天就会讲onmouseout对事件的影响 */
        // box.onmouseout = function(){
        //     for(var i = 0;i<liList.length;i++){
        //         animationSlow( liList[i] , {width:240});
        //     };
        // };
    </script>

</body>

</html>

3.万能函数防抖封装

  <script>

        //1.获取元素
        var liList = document.querySelectorAll('#box li');//li元素列表
        var box = document.querySelector('#box');//li元素列表

        //2.1 鼠标移入事件:当前li的宽度变成800,其他兄弟li宽度变成100
        for (var i = 0; i < liList.length; i++) {
            liList[i].onmouseover = function () {

                //this:事件源
                //调用防抖函数,  防抖间隔为500ms
                debonce(function () {
                    //3.事件处理函数 (触发事件)
                    console.log(1111);
                    //3.排他思想修改样式
                    for (var j = 0; j < liList.length; j++) {
                        if (liList[j] == this) {//是自己
                            animationSlow(liList[j], { width: 800 });
                        } else {
                            animationSlow(liList[j], { width: 100 });
                        }
                    };
                }.bind(this), 500);
            };
        };

        /**
        * @description: 万能防抖函数
        * @param {type} fn : 事件处理函数
        * @param {type} timeout : 防抖时间间隔
        * @return: 
        */
        function debonce(fn, timeout) {
            /* 核心技术点
            1.函数防抖需要使用定时器id, 这个id不能是局部的(函数走完之后会被回收)
            2.定时器id也不能是全局的,造成全局变量污染
            3.解决方案:
                (1)使用闭包延长局部变量生命周期,但是闭包语法很繁琐
                (2)利用函数本身也是对象,使用函数本身的静态成员来存储定时器ID
            */
            //1.先清除上一次触发事件 的定时器
            clearTimeout(debonce.timeID);
            //2.以最后一次触发 为准
            debonce.timeID = setTimeout(fn, timeout);

        };

    </script>

02-函数节流

1-函数节流介绍

  • 1.先理解什么是js中的高频事件
    • 高频事件 : 触发频率极高的事件。例如 滚动条事件onscroll 鼠标移动onmousemove 网页大小变化onresize等
      • 高频触发:事件本身不是高频的,但是用户可以通过很快的手速来触发。例如用户疯狂快速点击 抢购按钮(onclick,onmousedown)
  • ==2.高频事件的危害==
    • a.浪费资源,降低网页速度,甚至导致浏览器卡死:由于高频事件触发非常频繁,可能1秒钟会执行几十次甚至上百次,如果在这些函数内部,又调用了其他函数,尤其是操作了DOM (DOM操作耗性能且可能导致浏览器出现回流) ,不仅仅会降低整个网页的运行速度,甚至会造成浏览器卡死,崩溃。
    • b.网络堵塞 : 如果在高频事件中,进行了重复的ajax请求,可能会导致请求数据出现混乱,并且还占用服务器带宽增加服务器压力 (间接增加服务器成本)
  • ==3.函数节流介绍(throttle)==
    • 函数节流:限制事件处理函数的执行周期。
      • 例如:鼠标点击事件,限制0.5s内只能被触发一次。 无论你点击有多快,0.5s也只会执行一次。

2-函数节流解决思路

==限制事件处理函数的执行周期==

  • 1.声明变量 记录上一次 触发时间 (默认为0,保证事件第一次无条件触发)
  • 2.触发时间时:判断现在的时间 和 上一次时间 的间隔是否大于 节流间隔
  • 3.成功触发后 : 将现在的时间 变成 下一次时间 的参考时间
    //函数节流1 : 声明变量记录上一次事件触发时间
    var lastTime = 0;

    //1.1 鼠标移动事件
    var i = 0;
    window.onmousemove = function () {
        //函数节流2 : 判断触发时间间隔 (0.5秒 = 500ms)
        var currentTime = new Date().getTime(); //当前时间
        if (currentTime - lastTime > 500) {
            //事件处理函数
            i++;
            console.log('鼠标移动事件被触发' + i + '次');
            //函数节流3 : 事件成功触发之后,当前时间 成为 下一次触发间隔参考时间
            //说人话:现任女友已经结束了, 她已经成为下一任女友 故事里的 前任女友 
            lastTime = currentTime;
        };
    };、

3.万能节流函数的封装

  • 为什么要封装万能的节流函数
    • 在上一个小节中,我们的重点是介绍函数节流的思路。但是在实际开发中,每一个节流函数的事件处理都是不一样的,他们可能是鼠标移入、鼠标移出、鼠标移动。 每一个案例需要的节流间隔也不同
/**
* @description: 函数节流
* @param {type} fn: 事件处理函数
* @param {type} timeout: 节流间隔
* @return: 
*/
function throttle(fn, timeout) {
    /*核心技术介绍 
    1. 函数节流需要使用变量来存储  上一次触发时间
    2. 这个变量如果是局部变量 : 则函数完毕会被回收。 如果是全局变量:则会造成全局变量污染
    3.解决方案 : 利用函数本身也是对象,使用函数本身的静态成员来存储 上一次触发时间
    */

    //给throttle添加静态成员lastTime
    if(!throttle.lastTime){
        /* 为什么一定要有这一步呢?
        因为函数对象的属性不存在时,默认取值会得到undefined,而undefined在做数学计算
        的时候会转成number类型得到NaN. Number(undefined) 结果是NaN。无法计算
         */
        throttle.lastTime = 0;
    };

    //1.记录当前时间
    var currentTime = new Date().getTime();
    //2.判断触发时间间隔
    if (currentTime - throttle.lastTime > timeout) {
        fn();
        //3.将当前时间作为 下一次触发时间 参考时间
        throttle.lastTime = currentTime;
    };
};
//1.2 滚动条事件
var j = 0;
window.onscroll = function () {
    //节流函数 : 滚动条 0.5s 只会执行一次
    throttle(function () {
        j++;
        console.log('滚动事件被触发' + j + '次');
    }, 500);
};

//2. 高频触发 :事件本身触发不频繁, 但是用户可以通过疯狂触发来实现高频
var btn = document.querySelector('#btn');
var p1 = document.querySelector('#p1');
var k = 0;
btn.onclick = function () {
    //节流函数 : 按钮1s 只能点击一次
    throttle(function () {
        k++;
        p1.innerText = '点击事件被触发' + k + '次';
    }, 1000);
};

03-函数防抖和函数节流区别总结:

1.函数防抖:用户多次触发事件,以最后一次触发为准

2.函数节流:限制事件的执行周期(500ms内指挥执行一次)

3函数防抖与函数节流的异同点 与应用场景

(1)相同点:都是为了优化js代码执行频率,提高代码的性能

(2)不同点:

函数防抖:由用户需求决定

a.鼠标移入移出,用户快速移动鼠标,应该等用户结束后,以最后一次为主

b.输入框事件:验证手机号或者邮箱,用户输入时不断触发键盘事件,应该等用户结束输入之后,以最后一次输入为准

函数节流:由事件本身决定 (高频事件)

 a. onscroll : 高频事件(滚动条事件)

                     b. onmousemove:高频事件(鼠标移动)

                        * 特殊情况:高频触发  (抢购按钮点击,本身不是高频事件,用户可以用手速实现点击高频)

原创声明,本文系作者授权云+社区发表,未经许可,不得转载。

如有侵权,请联系 yunjia_community@tencent.com 删除。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 前端day19-JS高级(函数调用的上下文模式)学习笔记

    1.1复习函数三种调用方式:普通函数 对象方法 构造函数(理解this关键字作用:谁调用这个函数,this指向谁)

    帅的一麻皮
  • JavaScript高级语法补充(函数参数传递、in delete关键字、比较运算符隐式转换)

    1.值类型 (5种):  栈中存储的是数据,赋值时拷贝的也是数据。修改拷贝后的数据对原数据没有影响。

    帅的一麻皮
  • 前端day10-JS学习笔记(数组、函数、对象)

    3.变量取值: 函数名 (不会执行函数体代码,只是以字符串形式将变量中存储的东西打印出来而已)

    帅的一麻皮
  • 史上最详细的iOS之事件的传递和响应机制-实践篇前言

    VV木公子
  • 深兰科技陈海波:新零售的下一个战场是移动零售 | 镁客请讲

    对于深兰科技而言,不断推陈出新的技术和产品就是它的核心竞争力,如小兰、大兰、“小蚂哥”、芭堤雅等。

    镁客网
  • Mac开发跬步积累(五): Dark Mode下适配你的UI界面

    在macOS 10.9+ 的时候,苹果就提供了NSAppearance这个类来协助AppKit管理App的UI控件. NSAppearance决定着AppKit...

    代码行者
  • NodeJS错误处理最佳实践

    NodeJS的错误处理让人痛苦,在很长的一段时间里,大量的错误被放任不管。但是要想建立一个健壮的Node.js程序就必须正确的处理这些错误,而且这并不难学。如果...

    貟王軍
  • NameError: name 'xrange' is not defined

    于小勇
  • 3d 旋转(摘抄)

    我乃小神神
  • 直播预告 | 时序动作分析技术的研究与应用

    对于「时序」的分析和研究,一直是信息技术、物联网以及人工智能等技术与行业的热点领域。研究人员们也提出了针对序列化信息建模的各种方法,比如RNN、LSTM、GRU...

    优图实验室

扫码关注云+社区

领取腾讯云代金券