循序渐进,🚿一步步手写节流函数(记不住的快进来)

136 阅读3分钟

节流和防抖相信很多同学已经在很多场景使用到了,如果你还不知道是什么或者知道但是不知道如何去实现(记不住),那么请继续往下阅读,本次我们只讲节流

先简单介绍一下节流throttle:在触发任务的时候执行任务,并在接下来的一段时间(delay)内再次触发将不再执行。

举个浅显易懂的🌰:打王者荣耀的时候,我们按下下普通🐓键(实际上我们的手指疯狂点击),这时候角色会攻击出第一下,在某个间隔里面,我们的疯狂点击实际只有第一下生效了

image.png 以上就是所谓的节流了,接下来我们来看看怎么实现

1、一步步来,首先我们需要定义一个函数,这个函数接收两个参数 (fn:需要节流的函数,delay:需要延迟的时间),并且为了外部能够调用,这个函数返回一个函数

// 接受两个入参
function throttle(fn, delay){
	// 返回函数供外部调用
	return function() {
		
	}
}

2、接着是第一次调用直接执行

// 接受两个入参
function throttle(fn, delay){
	// 返回函数供外部调用
	return function() {
		fn()
	}
}

3、很明显上面代码每次调用都会执行,那么我们需要记录过去执行的时间记为last,

// 接受两个入参
function throttle(fn, delay){
        // 利用闭包,让外部调用的时候能够访问到同一个last变量
	let last = null
	// 返回函数供外部调用
	return function() {
		fn()
	}
}

4、现在我们有了last,初始为空,就可以开始写我们的逻辑了(无非就是控制什么时间点执行函数

// 接受两个入参
function throttle(fn, delay){
  // 记录上一次调用的时间,利用闭包,让外部调用的时候能够访问到同一个last变量
	let last
	// 返回函数供外部调用
	return function() {
		// 记录当前时间
                let now = +new Date() // 取时间戳
		// 第一次调用直接执行 || 上一次执行的时间已经距离现在>delay
		if(!last || last+delay<now) {
			last = now // 更新last为执行的时间
			fn()
		}
	}
}

以上,一个最简单的节流函数就实现了(实际代码不过10行)

我们在浏览器上就可以通过以下代码实现一个带节流的轮询了

let ajax = throttle(()=>{console.log(’节流’)}, 3000); setInterval(ajax, 1000)

5、接下来是优化,我们的函数可能需要传参,以上面ajax为例,让下面的代码输出 ”节流”

let ajax = throttle((name)=>{console.log(name)}, 3000); setInterval(ajax('节流'), 1000)

// 接受两个入参
function throttle(fn, delay){
  // 记录上一次调用的时间,利用闭包,让外部调用的时候能够访问到同一个last变量
	let last
	// 返回函数供外部调用
	return function() {
		// 函数的入参在这里
		let arg = arguments
		// 记录当前时间
                let now = +new Date() // 取时间戳
		// 第一次调用直接执行 || 上一次执行的时间已经距离现在>delay
		if(!last || last+delay<now) {
			last = now // 更新last为执行的时间
			fn(...arg)
		}
	}
}

image.png

6、最后我们让初始函数的this指向调用该函数的对象

// 接受两个入参
function throttle(fn, delay){
  // 记录上一次调用的时间,利用闭包,让外部调用的时候能够访问到同一个last变量
	let last
	// 返回函数供外部调用
	return function() {
		// 函数的入参在这里
		let arg = arguments
		// 存下当前的this
		let self = this
		// 记录当前时间
    let now = +new Date() // 取时间戳
		// 第一次调用直接执行 || 上一次执行的时间已经距离现在>delay
		if(!last || last+delay<now) {
			last = now // 更新last为执行的时间
			fn.call(self,...arg)
		}
	}
}

image.png

循序渐进,很容易就能手写出来了

扩展需求:要求最后一次点击之后,等计时器结束再执行一遍

7、这个时候我们的计时器就派上用场啦,给代码亿丢丢小小的改造

// 接受两个入参
function throttle(fn, delay){
  // 记录上一次调用的时间,利用闭包,让外部调用的时候能够访问到同一个last变量
	let last
	let timer // 添加定时器
	// 返回函数供外部调用
	return function() {
		// 函数的入参在这里
		let arg = arguments
		// 存下当前的this
		let self = this
		// 记录当前时间
                let now = +new Date() // 取时间戳
		// 第一次调用直接执行 || 上一次执行的时间已经距离现在>delay
		// if(!last || last+delay<now) {
		// 	last = now // 更新last为执行的时间
		// 	fn.apply(self,arg)
		// }
		*// 在时间间隔之内调用,清空并重新打开定时器,
		if(last && now<last+delay){
			clearTimeout(timer)
			timer = setTimeout(function(){
			last = now
			fn.call(self,...arg)
			},delay)
		}else{
			// 超过时间间隔的调用,直接执行
			last = now
			fn.call(self,...arg)
		}*
	}
}

以上,我们通过一步步完善最终实现了一个节流函数,下一次再回想起来,按着思路也能轻松手写出来啦!