js的节流和防抖的区别及联系

368 阅读5分钟

函数节流

浏览器中某些计算要比其它的昂贵很多,如果操作一个非常复杂的dom时就可能会进行很复杂的计算,连续进行过多的DOM操作就可能使浏览器挂起或崩溃。尤其在IE中使用onresize事件处理程序的时候容易发生,当调整浏览器大小的时候,该事件会连续触发,在onresize内部进行DOM操作的时候,其高频率的更改可能会让浏览器崩溃。每当这个时候,你就可以采用函数节流,节流的本质就是不让事件频繁的触发,在上一个事件触发的多少时间内不能重复的触发。

节流的本质:在第一次调用函数时,会创建一个定时器,在指定的时间间隔之后再运行代码。当第二次调用函数时,会清除前一次的定时器并设置另一个,如果前一个定时器已经执行过了,这个操作就没任何意义,然而,如果上一个定时器还未执行,其实就是将其替换为一个新的定时器,目的是只有在执行函数的请求停止了一定时间之后才执行。

var processor = {
  timeoutId: null,
  //实际进行处理的方法
  performProcessing: function () {
    //实际执行的代码
  },
  //初始处理调用的方法
  process: function () {
    clearTimeout(this.timeoutId);
    var that = this;
    this.timeoutId = setTimeout(function () {
      that.performProcessing()
    }, 100)
  }

}

//尝试开始执行
processor.process()

在这段代码中, 创建了一个叫做 processor 对象。 这个对象还有 2 个方法: process() 和 performProcessing()。前者是初始化任何处理所必须调用的,后者则实际进行应完成的处理。当调 用了 process(),第一步是清除存好的 timeoutId,来阻止之前的调用被执行。然后,创建一个新的 定时器调用 performProcessing()。由于 setTimeout()中用到的函数的环境总是 window,所以有 必要保存 this 的引用以方便以后使用。

时间间隔设为了 100ms,这表示最后一次调用 process()之后至少 100ms 后才会调用 perform- Processing()。所以如果 100ms 之内调用了 process()共 20 次,performanceProcessing()仍只 会被调用一次。

这个模式可以使用 throttle()函数来简化,这个函数可以自动进行定时器的设置和清除,如下例 所示:

function throttle(method, context) {
 clearTimeout(method.tId);
 method.tId= setTimeout(function(){
 method.call(context);
 }, 100);
}

throttle()函数接受两个参数:要执行的函数以及在哪个作用域中执行。上面这个函数首先清除 之前设置的任何定时器。定时器 ID 是存储在函数的 tId 属性中的,第一次把方法传递给 throttle() 的时候,这个属性可能并不存在。接下来,创建一个新的定时器,并将其 ID 储存在方法的 tId 属性中。 如果这是第一次对这个方法调用 throttle() 的话, 那么这段代码会创建该属性。 定时器代码使用 call()来确保方法在适当的环境中执行。如果没有给出第二个参数,那么就在全局作用域内执行该方 法。

例如在改变浏览器大小的情况下,需要设置一个div的宽高相等,如果这个div中大量的布局代码及css,那么就会进行大量的计算导致浏览器崩溃,这时可以采用节流的方法来改善:


function resizeDiv(){
    var div = document.getElementById("myDiv");
    div.style.height = div.offsetWidth + "px"; 
}
 
 window.onresize = function(){
    throttle(resizeDiv);
};

这里,调整大小的功能被放入了一个叫做 resizeDiv()的单独函数中。然后 onresize 事件处理 程序调用 throttle()并传入 resizeDiv 函数,而不是直接调用 resizeDiv()。多数情况下,用户是 感觉不到变化的,虽然给浏览器节省的计算可能会非常大。

函数防抖

防抖: 是在输入框输入内容时,在不超过时间间隔时不会去打印或请求接口,超过时间间隔再去请求

//模拟一段ajax请求 没用防抖函数之前,每输入一个字母都会打印,极大的浪费浏览器资源

function ajax(content) {
  console.log('ajax request ' + content)
}

let inputa = document.getElementById('unDebounce')

inputa.addEventListener('keyup', function (e) {
  ajax(e.target.value)
}) 

运行:打开控制台可以看到每输入一个字母都会打印,说明每输入一个字母都会去请求接口,这显然是不合理的,浪费资源。

//在实际的应用中,用户往往时输入完成时再去请求接口,下面利用防抖优化一下 //模拟一段ajax请求

function ajax(content) {
  console.log('ajax request ' + content)
}

function debounce(fun, delay) {
  return function (args) {
    let that = this
    let _args = args
    // console.log('args: ', args);

    clearTimeout(fun.id)
    fun.id = setTimeout(function () {
      fun.call(that, _args)
    }, delay)
  }
}

let inputb = document.getElementById('debounce')

let debounceAjax = debounce(ajax, 500)

inputb.addEventListener('keyup', function (e) {
  debounceAjax(e.target.value)
})

可以看到,我们加入了防抖以后,当你在频繁的输入时,并不会发送请求,只有当你在指定间隔内没有输入时,才会执行函数。如果停止输入但是在指定间隔内又输入,会重新触发计时。 下面再看一个例子:

// setInterval(debounce(boom,2000),1000) 根本就没有执行,因为间隔是2s,而执行只需要1s,每次去打印时定时器都会重新计时

let biu = function () {
  console.log('biu biu biu', new Date().Format('HH:mm:ss'))
}

let boom = function () {
  console.log('boom boom boom', new Date().Format('HH:mm:ss'))
}


setInterval(debounce(biu, 500), 1000)
setInterval(debounce(boom, 2000), 1000)

参考链接: juejin.cn/post/684490…