面试官问你手写防抖函数怎么实现立即执行?我:immediate!!!

1,552 阅读5分钟

前言

当你进行页面请求的时候,可能会由于页面卡顿的原因导致页面没有及时进行响应,你可能会一直点一直点,这个时候就会请求多次,那么就会有很多次执行,这个时候我们应该通过防抖的方法来将执行推到最后一次,把之前的触发全部取消,从而减少执行的次数,页面的卡顿,是前端性能优化最重要的点。并且防抖函数是基于闭包的,那么下面让我们通过一个小实例来介绍一下怎么实现防抖呢?

正文

  • 定义一个container容器,
  • 为其绑定一个鼠标移动事件,实现每次在container容器移动鼠标的时候,其innerHTML的值进行加一,通过定义一个变量count来实现
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        #container{
            width: 100%;
            height: 200px;
            line-height: 200px; /* 文字垂直居中 */
            text-align: center;
            color: aliceblue;
            background-color: #bfa;
            font-size: 30px;
        }
    </style>
</head>
<body>
    <div id="container"></div>
    <script>
        var count = 1;
        var container = document.getElementById('container');
        // 事件处理函数
        function getUserAction(){
            container.innerHTML = count++;
        }
        // DOM 0级事件
        // addEventListener DOM 2 mousemove
        
        container.onmousemove = getUserAction;
    </script>
</body>
</html>

image.png

那么现在我们要为该事件写个防抖的函数,不能一直在container容器移动鼠标innerHTML的值一直加一,应该通过防抖的方法来实现,在一秒内鼠标不再移动后就实现加一,在应用中也就是通过防抖的方法来将执行推到最后一次,把之前的触发全部取消,从而减少执行的次数。

js思路:

  • 通过id获取container容器,再定义一个变量count来实现container容器的innerHTML加一
  • 定义一个防抖函数debounce,在该函数定义两个形参func:事件处理函数、wait:等待wait长度时间内在container容器内没有移动鼠标,就触发事件处理函数
  • 在该函数内定义一个变量timeout,该变量用来获取定时器setTimeout的返回值,也就是定时器的id
  • 在该函数内会返回一个函数,返回的函数在container触发鼠标移动时间时会调用(会形成闭包,因为当执行返回的函数时,即内部函数,需要调用外部函数debounce中的变量timeout,也就是当外部函数debounce执行完时,会形成一个闭包,该闭包包含变量timeout)
  • 内部函数:定义两个变量context、args用于接收this和argument,这里的this指的是container容器,因为该内部函数为事件执行函数,用于控制执行的次数,也就是当鼠标在container容器内移动的时候,就触发调用了该函数,并且该事件执行函数是container调用的
  • 先销毁上一次的定时器,也就是把前面所有的触发鼠标移动事件的请求全部清除,再调用setTimeout函数把返回的定时器的id赋值给timeout,定时器的时间为wait,也就是在wait时间内没有鼠标移动时间的触动了,就去调用事件处理函数getUserAction
  • 在定时器函数中通过apply实现显示绑定,将context和args传递给func函数,即事件处理函数getUserAction(原来事件处理函数的this指向container,但是设置防抖函数后,this指向了window,所有通过apply来指向container)
 <script>
        var count = 1;
        var container = document.getElementById('container');
        // 事件处理函数
        function getUserAction(){
            console.log(this, arguments, 'bbbbbb');
            container.innerHTML = count++;
        }
        // DOM 0级事件
        // addEventListener DOM 2 mousemove
        // 减少次数?
        function debounce(func, wait) {
            // 闭包
            var timeout;
             return function (event) {
                // 事件执行函数 this --> container
                // 控制执行的次数
                var context = this;
                var args = arguments;

                clearTimeout(timeout);
                // func this --> 普通函数来运行 window
                timeout = setTimeout(function() {
                    func.apply(context, args);
                }, wait);// 返回定时器的id,id在timeout的自由变量中
            }
        }
        container.onmousemove = debounce(getUserAction, 1000);
    </script>

可能回答到这面试官还会继续问你,如果我想让你让鼠标移动事件立即触发一次,也就是一开始就执行事件处理函数getUserAction,你怎么实现?

  • 给debounce防抖函数传入第三个形参immediate,用于判断是否立即执行,
  • 在返回的函数中先判断timeout是否为undefined,如果不为undefined,就销毁之前的定时器
  • 判断传入的形参immediate是否为true,为true就定义一个变量callNow,把!timeout赋值给callNow,如果timeout为undefined,也就是还没有鼠标移动时,callNow为true,如果鼠标移动了,callNow就为false
  • 调用setTimeout,里面的函数执行的内容为将timeout变为null,时间为wait,也就是等待wait时间后,把倒数第二次和之前的定时器全部清除掉
  • 判断callNow,如果为true,也就是还没有进行鼠标移动之前,就执行事件处理函数getUserAction,从而达到了立即执行的效果
<script> 
        var count = 1;
        var container = document.getElementById('container');
        function getUserAction(){
            console.log(this, arguments, 'bbbbbb');
            container.innerHTML = count++;
        }

        // 防抖功能函数 为了性能优化 1/60 (屏幕刷新率)
        // func 是真正要执行的处理函数, this, args权力
        // wait 定时器 id clear, 最后一次
        // immediate 立即执行一次
        function debounce(func, wait, immediate) {
            // 自由变量空间
            var timeout, result;

            // 真正执行的函数
            return function() {
               // 二传手
               var context = this;
               var args = arguments;

               if (timeout) clearTimeout(timeout);

               if (immediate) {
                   var callNow = !timeout;
                   timeout = setTimeout(function() {
                       timeout = null;// 把倒数第二次的定时器清除掉
                   }, wait);
                   if (callNow) result = func.apply(context, args); 
               }else {
                timeout = setTimeout(function() {
                    result = func.apply(context, args);
                }, wait);
               }
               return result;
               console.log(result);
            } 
        }
        container.onmousemove = debounce(getUserAction, 1000, true);
    </script>

结语

手写防抖函数传入三个形参的方法你get到了吗?快点拿下面试官~

image.png