3

防抖节流 是前端在优化性能问题上,经常使用的两种技术手段。比如 inputscrollresizemousemove 等事件,如果不加以控制,频繁的触发,无疑将会带来额外的性能开销,极端情况下,可能造成死机卡死现象。今天,我们我一起来聊聊他们吧。

默认情况

在讨论防抖和节流之前,我们先来看看,在不做任何处理情况下,一个事件频繁触发,会是怎样的一种情况。

假如有如下代码:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Document</title>
  <style>
    #content {
      width: 200px;
      padding: 50px;
      color: #fff;
      font-size: 30px;
      text-align: center;
      background-color: #8597a3;
    }
  </style>
</head>
<body>
  <div id="content">0</div>
  <script></script>
</body>
</html>

有如下 js:

var count = 0;
var el = document.getElementById('content');
    
function handle() {
  el.innerHTML = ++count;
}

el.onmousemove = handle;

当鼠标在元素 div 中移动的时候,效果如下:

event 事件触发 与 handle 事件的执行,关系如下:

防抖

防抖,也叫去抖动,就是当事件快速连续不断触发时,动作只会执行一次。

也可以这么理解:就是指触发事件后在 n 秒内函数只能执行一次,如果在 n 秒内又触发了事件,则会重新计算函数执行时间。

防抖函数分为,延迟防抖前缘防抖,区别就是是否会立即执行。

延迟防抖

var count = 0;
var el = document.getElementById('content');

var debounce = function(fn, delay) {
  var timer = null;
  return function() {
    if(timer) {
      clearTimeout(timer)
    }
    timer = setTimeout(fn, delay)
  }
};
    
function handle() {
  el.innerHTML = ++count;
}

el.onmousemove = debounce(handle, 300);

效果如下:

event 事件触发 与 handle 事件的执行,关系如下:

event 事件触发时,延迟一定时间触发 handle 事件。

上面代码,有个问题,就是 handle 函数,接收不到 event 对象,所以,需要改进下:

var count = 0;
var el = document.getElementById('content');

var debounce = function(fn, delay) {
  var timer = null;
  return function() {
    var context = this;
    var args = arguments;
    if(timer) {
      clearTimeout(timer)
    }
    timer = setTimeout(function() {
      fn.apply(context, args)
    }, delay)
  }
};
    
function handle(e) {
  console.log(e);
  el.innerHTML = ++count;
}

el.onmousemove = debounce(handle, 300);

前缘防抖

var count = 0;
var el = document.getElementById('content');

var debounce = function(fn, delay) {
  var timer = null;
  return function() {
    var context = this;
    var args = arguments;
    if(timer) {
      clearTimeout(timer)
    }

    if(!timer) {
      fn.apply(context, args)
    }
    timer = setTimeout(function() {
      timer = null;
    }, delay)
  }
};
    
function handle(e) {
  console.log(e);
  el.innerHTML = ++count;
}

el.onmousemove = debounce(handle, 300);

效果如下:

event 事件触发 与 handle 事件的执行,关系如下:

节流

节流,就是当事件快速连续不断触发时,动作会按照一定时间规律执行。

节流有两种写法,时间戳写法 和 定时器写法。

时间戳写法

var count = 0;
var el = document.getElementById('content');

var throttle = function(fn, delay) {
  var begin = 0
  return function() {
    var context = this;
    var args = arguments;
    var now = Date.now();
    var timespend = now - begin;

    if(timespend >= delay) {
      fn.apply(context, arguments)
      begin = now;
    }
  };
}
    
function handle(e) {
  console.log(e);
  el.innerHTML = ++count;
}

el.onmousemove = throttle(handle, 1000);

效果如下:

event 事件触发 与 handle 事件的执行,关系如下:

注意上图,开头 handle 事件,event 事件刚触发,handle 事件就执行了。也就是说,时间戳,实现的节流,handle 事件是在给定时间段的开头执行。

结尾浅色 handle 事件不执行,原因是,如果给定的间隔时间是1s,而只连续触发了 0.5s ,没达到指定间隔,因此不执行。

下面再来看定时器写法。

定时器写法

var count = 0;
var el = document.getElementById('content');

var throttle = function(fn, delay) {
  var timer = null;
  return function() {
    var context = this;
    var args = arguments;

    if(!timer) {
      timer = setTimeout(function() {
        timer = null;
        fn.apply(context, args);
      }, delay);
    }
  };
}
    
function handle(e) {
  console.log(e);
  el.innerHTML = ++count;
}

el.onmousemove = throttle(handle, 1000);

效果如下:

event 事件触发 与 handle 事件的执行,关系如下:

从动图上可以看到,第 7 秒的时候,鼠标已经离开,过一会,仍然会跳到 8 。说明,定时器,实现的节流,handle 事件是在给定时间段的结尾执行。

参考链接

函数防抖和节流

防抖(debounce) 和 节流(throttling)

js防抖和节流


zhangjinpei
103 声望6 粉丝

做一枚精致的前端er