手拉手带你实现防抖与节流

本文介绍了防抖和节流的概念及其应用场景,如输入框搜索、按钮点击和滚动事件。接着详细讲解了防抖函数的实现,包括基本功能、this和参数绑定、取消功能、立即执行和获取返回值的优化。同样,文章也探讨了节流函数的实现,包括基本功能、this和参数绑定、立即执行、尾调用执行、取消执行以及获取返回值的功能。这些技术常用于前端开发中的性能优化,以避免因频繁事件触发导致的性能问题。
摘要由CSDN通过智能技术生成

概要

防抖与节流在前端中是必须要掌握的知识,如果你还不会的话,那我们就嘴对嘴,哦,不不不!那我们就手拉手一起实现一下吧!

防抖

啥子是防抖呢? 就是触发了一个事件,但是这个事件对应的函数并不会立即执行,而是会等待固定的时间t后才会执行。如果在t时间内再次触发了这个事件,则需要取消上次的函数执行,再次等待t时间后执行。具体流程如下:
在这里插入图片描述
在这里插入图片描述

应用场景

1.输入框中频繁的提交内容,搜索,提交信息
2.频繁的点击按钮,触发某个事件
3.监听浏览器滚动事件,完成特定操作
4.用户缩放浏览器的resize事件

1. 防抖的基本功能实现

function debounce(fn, delay) {
	// 1.标志位,用来记录上一次事件触发的timer
	let timer = null
	/* 
	   为了使小白第一次看这个也能看懂,这里解释的再详细一点。当第一次调用时,timer为null, 
	   因此Boolean(timer) = false,所以clearTimeout(timer)不会执行,即函数不会取消执行,
	   而是等待delay时间片后执行。
	   但是如果在这段时间内再次触发了debounce,则取消上次函数的执行,重新等待delay后执行。
	   不知道你有没有这样的疑问,在一个delay时间片内再次触发,setTimeout()不是还没出结果,
	   那么timer不是还是null吗,那么clearTimeout不会执行啊,如何取消上次的执行呢?
	   这里如果有疑问的话,说明你的JS还有待提高哦。
	   首先,setTimeout确实是负责延迟执行函数的,但他延迟执行的是传入的回调函数,而不是自身。
	   其次,setTimeout由window负责调用,在代码执行过程中会被加入到宏任务队列中,
	   在微任务执行完过后,就开始依次执行宏任务。当执行到它时,会立即执行并返回一个timeoutId。
	   因此,在delay时间片内再次触发时,timer已经有值了,所以就可以取消上次的函数执行了。
	 */
	// 2.触发事件执行的函数
    const myDebounce = () => {
    	// 若等待时间内再次调用,则取消函数执行
        if(timer)   clearTimeout(timer)
        // 否则等待delay时间后执行函数
        timer = setTimeout(() => {
            fn()
            timer = null
        }, delay)
    }
    return myDebounce
}

2. 关于this和参数绑定的优化

function debounce(fn, delay) {
	// 1.标志位,用来记录上一次事件触发的timer
	let timer = null
	// 2.触发事件执行的函数
    const myDebounce = (...args) => {
    	// 若等待时间内再次调用,则取消函数执行
        if(timer)   clearTimeout(timer)
        // 否则等待delay时间后执行函数
        timer = setTimeout(() => {
            fn.call(this, ...args)
            // 或 fn.apply(this, args)
            timer = null
        }, delay)
    }
    return myDebounce
}

3. 关于取消功能的实现

function debounce(fn, delay) {
	// 1.标志位,用来记录上一次事件触发的timer
	let timer = null
	// 2.触发事件执行的函数
    const myDebounce = (...args) => {
    	// 若等待时间内再次调用,则取消函数执行
        if(timer)   clearTimeout(timer)
        // 否则等待delay时间后执行函数
        timer = setTimeout(() => {
            fn.call(this, ...args)
            // 或 fn.apply(this, args)
            timer = null
        }, delay)
    }
    // 3.给myDebounce绑定一个取消的函数
    myDebounce.cancle = () => {
    	if(timer)   clearTimeout(timer)
    }
    return myDebounce
}

4. 关于立即执行功能的实现

// 原则:一个函数用来执行一件事,一个变量用来记录一种状态
function debounce(fn, delay, immediate = false) {
	// 1.标志位,用来记录上一次事件触发的timer
	let timer = null
	// 用来控制防止一个时间片内多次触发而多次执行
	let isInvoke = false
	// 2.触发事件执行的函数
    const myDebounce = (...args) => {
    	// 若等待时间内再次调用,则取消函数执行
        if(timer)   clearTimeout(timer)
		// 判断是否需要立即执行
		if(immediate && !isInvoke) {  // 如果不需要延迟,立即执行
			fn.apply(this, args)
			isInvoke = true
			return 
		} else { // 延迟执行
			 // 否则等待delay时间后执行函数
        	timer = setTimeout(() => {
            	fn.call(this, ...args)
            	// 或 fn.apply(this, args)
            	timer = null
            	isInvoke = false
        	}, delay)
		} 
    }
    // 3.给myDebounce绑定一个取消的函数
    myDebounce.cancle = () => {
    	if(timer)   clearTimeout(timer)
    	timer = null
    	isInvoke = false
    }
    return myDebounce
}

5. 关于获取返回值功能的实现

// 原则:一个函数用来执行一件事,一个变量用来记录一种状态
function debounce(fn, delay, immediate = false, resultCallback) {
	// 1.标志位,用来记录上一次事件触发的timer
	let timer = null
	// 用来控制防止一个时间片内多次触发而多次执行
	let isInvoke = false
	// 2.触发事件执行的函数
    const myDebounce = function(...args) {
    	return new Promise((resolve, reject) => {
    		try {
    			// 若等待时间内再次调用,则取消函数执行
        		if(timer)   clearTimeout(timer)
				// 判断是否需要立即执行 
				if(immediate && !isInvoke) {  // 如果不需要延迟,立即执行
					const res = fn.apply(this, args)
					if(resultCallback)	resultCallback(res)
					resolve(res)
					isInvoke = true
					return 
				} else {  // 延迟执行
					// 等待delay时间后执行函数
       				timer = setTimeout(() => {
         	    		const res = fn.call(this, ...args)
            			// 或 fn.apply(this, args)
            			if(resultCallback)	resultCallback(res)
						resolve(res)
            			timer = null
            			isInvoke = false
        			}, delay) 
				} 
    		} catch(error) {
    			reject(error)
    		}
    	})
    	
    }
    // 3.给myDebounce绑定一个取消的函数
    myDebounce.cancle = () => {
    	if(timer)   clearTimeout(timer)
    	timer = null
    	isInvoke = false
    }
    return myDebounce
}

节流

那啥子是节流呢?就是在一个时间片内无论触发多少次事件,该事件也只会执行一次。具体流程如下:
在这里插入图片描述
在这里插入图片描述

应用场景

1.监听页面的滚动事件
2.鼠标移动事件
3.用户频繁点击按钮操作
4.游戏中的一些设计

1. 基本功能实现

function throttle(fn, interval) {
  // 1.记录上一次的开始时间
  let lastTime = 0
  // 2.事件触发时, 真正执行的函数
  const myThrottle = function() {
    // 2.1.获取当前事件触发时的时间
    const nowTime = new Date().getTime()
    // 2.2.使用当前触发的时间和之前的时间间隔以及上一次开始的时间, 计算出还剩余多长事件需要去触发函数
    const remainTime = interval - (nowTime - lastTime)
    if (remainTime <= 0) {
      // 2.3.真正触发函数
      fn()
      // 2.4.保留上次触发的时间
      lastTime = nowTime
    }
  } 
  return myThrottle 
}

2. 关于this和参数绑定的实现

function throttle(fn, interval) {
  // 1.记录上一次的开始时间
  let lastTime = 0
  // 2.事件触发时, 真正执行的函数
  const myThrottle = function(...args) {
    // 2.1.获取当前事件触发时的时间
    const nowTime = new Date().getTime()
    // 2.2.使用当前触发的时间和之前的时间间隔以及上一次开始的时间, 计算出还剩余多长事件需要去触发函数
    const remainTime = interval - (nowTime - lastTime)
    if (remainTime <= 0) {
      // 2.3.真正触发函数
      fn.apply(this, args)
      // 2.4.保留上次触发的时间
      lastTime = nowTime
    }
  } 
  return myThrottle 
}

3. 关于立即执行功能的实现

function throttle(fn, interval, options = { leading: true }) {
  // 1.记录上一次的开始时间
  let lastTime = 0
  const { leading } = options

  // 2.事件触发时, 真正执行的函数
  const myThrottle = function(...args) { 
    // 2.1.获取当前事件触发时的时间
    const nowTime = new Date().getTime()
    // 对立即执行功能进行控制
    if (!lastTime && !leading) lastTime = nowTime

    // 2.2.使用当前触发的时间和之前的时间间隔以及上一次开始的时间, 
    // 计算出还剩余多长事件需要去触发函数
    const remainTime = interval - (nowTime - lastTime)
    if (remainTime <= 0) {
      // 2.3.真正触发函数
      fn.apply(this, args)
      // 2.4.保留上次触发的时间
      lastTime = nowTime
    }
  } 
  return myThrottle 
}

4. 关于尾调用执行功能的实现

function throttle(fn, interval, options = { leading: true, trailing: false }) {
  // 1.记录上一次的开始时间
  let lastTime = 0
  const { leading, trailing } = options
  let timer = null

  // 2.事件触发时, 真正执行的函数
  const myThrottle = function() { 
    // 2.1.获取当前事件触发时的时间
    const nowTime = new Date().getTime()
    // 对立即执行功能进行控制
    if (!lastTime && !leading) lastTime = nowTime

    // 2.2.使用当前触发的时间和之前的时间间隔以及上一次开始的时间, 
    // 计算出还剩余多长事件需要去触发函数
    const remainTime = interval - (nowTime - lastTime)
    if (remainTime <= 0) {
      if (timer) {
        clearTimeout(timer)
        timer = null
      } 
      // 2.3.真正触发函数
      fn.apply(this, args)
      // 2.4.保留上次触发的时间
      lastTime = nowTime
      return
    }

    if (trailing && !timer) {
      timer = setTimeout(() => {
        timer = null
        lastTime = !leading ? 0: new Date().getTime()
        fn.apply(this, args)
      }, remainTime)
    }
  } 
  return myThrottle 
}

5. 关于取消执行功能的实现

function throttle(fn, interval, options = { leading: true, trailing: false }) {
  // 1.记录上一次的开始时间
  let lastTime = 0
  const { leading, trailing } = options
  let timer = null

  // 2.事件触发时, 真正执行的函数
  const myThrottle = function(...args) {
    // 2.1.获取当前事件触发时的时间
    const nowTime = new Date().getTime()
    // 对立即执行功能进行控制
    if (!lastTime && !leading) lastTime = nowTime
    // 2.2.使用当前触发的时间和之前的时间间隔以及上一次开始的时间, 
    // 计算出还剩余多长事件需要去触发函数
    const remainTime = interval - (nowTime - lastTime)
    if (remainTime <= 0) {
      if (timer) {
        clearTimeout(timer)
        timer = null
      }
      // 2.3.真正触发函数
      fn.apply(this, args)
      // 2.4.保留上次触发的时间
      lastTime = nowTime
      return
    }
    if (trailing && !timer) {
      timer = setTimeout(() => {
        timer = null
        lastTime = !leading ? 0: new Date().getTime()
        fn.apply(this, args)
      }, remainTime)
    }
  }

  myThrottle.cancel = function() {
    if(timer) clearTimeout(timer)
    timer = null
    lastTime = 0
  }

  return myThrottle 
}

6. 关于获取函数返回值功能的实现

function throttle(fn, interval, options = { leading: true, trailing: false }) {
  // 1.记录上一次的开始时间
  const { leading, trailing, resultCallback } = options
  let lastTime = 0
  let timer = null

  // 2.事件触发时, 真正执行的函数
  const myThrottle = function(...args) {
    return new Promise((resolve, reject) => {
      // 2.1.获取当前事件触发时的时间
      const nowTime = new Date().getTime()
      // 对立即执行功能进行控制
      if (!lastTime && !leading) lastTime = nowTime

      // 2.2.使用当前触发的时间和之前的时间间隔以及上一次开始的时间, 
      // 计算出还剩余多长事件需要去触发函数
      const remainTime = interval - (nowTime - lastTime)
      if (remainTime <= 0) {
        if (timer) {
          clearTimeout(timer)
          timer = null
        }

        // 2.3.真正触发函数
        const result = fn.apply(this, args)
        if (resultCallback) resultCallback(result)
        resolve(result)
        // 2.4.保留上次触发的时间
        lastTime = nowTime
        return
      }

      if (trailing && !timer) {
        timer = setTimeout(() => {
          timer = null
          lastTime = !leading ? 0: new Date().getTime()
          const result = fn.apply(this, args)
          if (resultCallback) resultCallback(result)
          resolve(result)
        }, remainTime)
      }
    })
  }

  myThrottle.cancel = function() {
    if(timer) clearTimeout(timer)
    timer = null
    lastTime = 0
  }

  return myThrottle 
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值