函数的防抖(debounce)和节流(throttle)

应用场景

防抖(debounce)

  1. search搜索联想,用户在不断输入值时,用防抖来节约请求资源。
  2. window触发resize的时候,不断的调整浏览器窗口大小会不断的触发这个事件,用防抖来让其只触发一次

节流(throttle)

  1. 鼠标不断点击触发,mousedown(单位时间内只触发一次)
  2. 懒加载监听滚动事件,比如是否滑到底部自动加载更多,用throttle来判断

防抖实现

多次触发的事件,在设定的时间间隔内,只执行最后一次,或者只执行最开始的一次,如果时间间隔没到又触发,那就重新开始计时。举个例子,比如时间间隔设为1秒钟,第一次触发会开始计时,如果在1秒内再次触发,就会重新计时,只有1秒内没有再触发才会执行一次。

下面是高程中的经典代码,第一次执行的时候宏任务队列为空,没有setTimeout,所以clearTimeout没有作用,然后setTimeout被加入宏任务队列。如果没有继续触发,那么过500毫秒后将执行这个方法。如果在500毫秒内触发,那么之前添加进宏任务队列的setTimeout会被clearTimeout给去掉,然后将新的setTimeout推入宏任务队列,然后重新开始计时。

function debounce(method, context) {
  clearTimeout(method.tId);
  method.tId = setTimeout(function() {
    method.call(context)
  }, 500)
}

使用闭包记录一下是否被触发过,然后匿名函数在全局下执行会导致this和arguments丢失,所以同样需要使用闭包保存一下。

非立即执行版:触发事件后函数不会立即执行,而是在 n 秒后执行,如果在 n 秒内又触发了事件,则会重新计算函数执行时间

function debounce(func, wait) {
    let timer;
    return function() {
      let that = this; // 注意 this 指向
      let args = arguments; // arguments中存着e
         
      if (timer) clearTimeout(timer);
 
      timer = setTimeout(() => {
        func.apply(that, args)
      }, wait)
    }
}
content.onmousemove = debounce(count,1000);

立即执行版:触发事件后函数会立即执行,然后 n 秒内不触发事件才能继续执行函数的效果

function debounce(func, wait) {
    let timer;
    return function() {
      let that = this; // 这边的 this 指向谁?
      let args = arguments; // arguments中存着e
 
      if (timer) clearTimeout(timer);
 
      let callNow = !timer;
 
      timer = setTimeout(() => {
        timer = null;
      }, wait)
 
      if (callNow) func.apply(that, args);
    }
}

节流实现

节流就是将单位时间内的多次触发合并为一次,即n秒内执行一次,2n秒内执行2次。

同样需要使用闭包,一是记录上一次触发的时间,二是保存函数执行的this和arguments

时间戳版

function throttle(func, wait) {
    let previous = 0;
    return function() {
      let now = Date.now();
      let that = this;
      let args = arguments;
      if (now - previous > wait) {
        func.apply(that, args);
        previous = now;
      }
    }
}
content.onmousemove = throttle(count,1000);

定时器版

function throttle(func, wait) {
    let timeout;
    return function() {
      let that = this;
      let args = arguments;
      if (!timeout) {
        timeout = setTimeout(() => {
          timeout = null;
          func.apply(that, args)
        }, wait)
      }
    }
}

关于闭包中this指向问题

首先明确一下,函数中 this 永远指向调用它的对象,谁调用它就指向谁。

使用 obj 对象调用 getName ,因此 this 指向 obj 对象,obj.getName() 输出 My obj

第二个 foo 函数实际上是由顶层 window 对象调用的,等价于 this.foo() ,因此 this 指向 window 对象,foo() 输出 The Window

var name = "The Window";
var obj = {
	name: 'My obj',
	getName: function() {
		console.log(this.name);
	}
}
obj.getName(); // My obj
const foo = obj.getName;
foo(); // The Window

因此在下面的代码中,当使用 obj 去调用 getName 方法时,getName 方法的 this 指向 obj 对象。

但是 getName 方法里面还返回了另一个函数,且这是一个匿名函数,匿名函数的执行环境具有全局性,因此匿名函数中的 this 常常指向 window 。

var name = "The Window";
var obj = {
	name: 'My obj',
	getName: function() {
		console.log(this.name); // My obj
		return function() {
			console.log(this.name); // The Window
		}
	}
}
obj.getName()();

var 声明的变量会挂载到 window 对象上,因此可以使用 this 访问

为了防止匿名函数中的 this 丢失,一种解决方案是使用闭包将 this 保存一下。

var name = "The Window";
var obj = {
	name: 'My obj',
	getName: function() {
		var that = this; // 使用 that 变量保存 this
		return function() {
			console.log(this.name); // My obj
		}
	}
}
obj.getName()();

还有一种方法是使用 ES6 中的箭头函数。跟匿名函数一样,箭头函数没有自己的 this ,但是箭头函数会自动捕获并固定上级词法作用域的 this

var name = "The Window";
var obj = {
	name: 'My obj',
	getName: function() {
		return () => {
			console.log(this.name); // My obj
		}
	}
}
obj.getName()();

参考:
js 函数的防抖(debounce)与节流(throttle)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值