前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >JavaScript网页性能优化(函数防抖与函数节流)

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

原创
作者头像
帅的一麻皮
修改2020-05-18 11:17:25
1.4K0
修改2020-05-18 11:17:25
举报
文章被收录于专栏:前端与Java学习前端与Java学习

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()动态修改为当前事件源
代码语言:javascript
复制
<!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.万能函数防抖封装

代码语言:javascript
复制
  <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.成功触发后 : 将现在的时间 变成 下一次时间 的参考时间
代码语言:javascript
复制
    //函数节流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.万能节流函数的封装

  • 为什么要封装万能的节流函数
    • 在上一个小节中,我们的重点是介绍函数节流的思路。但是在实际开发中,每一个节流函数的事件处理都是不一样的,他们可能是鼠标移入、鼠标移出、鼠标移动。 每一个案例需要的节流间隔也不同
代码语言:javascript
复制
/**
* @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;
    };
};
代码语言:javascript
复制
//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:高频事件(鼠标移动)

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

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

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

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

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

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 01-函数防抖
    • 1.什么是函数防抖? (debounce)
      • 2.函数防抖解决思路
        • 3.万能函数防抖封装
        • 02-函数节流
          • 1-函数节流介绍
            • 2-函数节流解决思路
              • 3.万能节流函数的封装
              • 03-函数防抖和函数节流区别总结:
              领券
              问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档