节流、防抖以及这段时间来的感受

slince 于 2024-05-30 发布

这几天复习到了两个概念,一个是节流,一个是防抖,他们都是可以用于浏览器性能优化。

不过在说明节流和防抖的概念之前,先说一下 callapplybind 这三个函数

call、apply、bind

这三个函数都是 JS 中函数的方法,是用来改变函数调用时的 this 绑定并传递参数,换句话来说,就是改变 this 的指向。它允许你在不同的上下文中调用一个函数,这些方法都是 Function 原型,所以所有函数都可以直接调用它们。

下面来分别看看它们仨

call

call 方法调用一个函数,并指定  this  的值和单个参数列表。

func.call(thisArg, arg1, arg2, ...)

示例

function greet(intro, time) {
  return `${intro}--${this.name},${this.age}--${time}`
}

const person = {
  name: 'Tim',
  age: 20,
}

console.log(greet.call(person, '你好', '2024')) // 你好--Tim,20--2024

分析:注意看,return 里面的内容用到了 this,如果没有使用 call 改变 this 指向的话,那么 this 的指向应该是 window。

apply

apply  方法与  call  方法类似,但它接受一个参数数组(或类数组对象)作为第二个参数。

func.apply(thisArg, [argsArray])
function greet(intro, time) {
  return `${intro}--${this.name},${this.age}--${time}`
}

const person = {
  name: 'Tim',
  age: 20,
}

console.log(greet.apply(person, ['你好', '2024'])) // 你好--Tim,20--2024
function add(a, b) {
  return a + b
}
const numbers = [2, 3]
console.log(add.apply(null, numbers)) // 输出: 5

可以看到 apply 和 call 的区别很小,只不过是参数变成了数组

bind

bind  方法创建一个新的函数,在调用时将  this  绑定到提供的值,并在新的函数中预设一定的参数。

const boundFunc = func.bind(thisArg, arg1, arg2, ...)
function greet(intro, time) {
  return `${intro}--${this.name},${this.age}--${time}`
}

const person = {
  name: 'Tim',
  age: 20,
}
const bindFun = greet.bind(person, '你好啊🤔')
console.log(bindFun('2024~~')) // 你好啊🤔--Tim,20--2024~~

总结

使用场景

接下来我们来自己实现一个 call 方法

Function.prototype.myCall = function (context) {
  // 检查调用者是否是函数
  if (typeof this !== 'function') {
    throw new TypeError('Error')
  }

  // 如果没有提供 context,默认使用全局对象(浏览器中是window,这里是非严格模式,严格模式是undefined)
  context = context || windows

  // 将当前函数(即this)作为 context 对象的一个属性fn,这样,我们就可以通过 context.fn 调用这个函数,并确保 this 指向 context 对象
  context.fn = this

  // 获取传递的参数
  // arguments 是一个类数组对象,包含传递给 myCall 的所有参数。使用扩展运算符 ... 将其转换为真正的数组,并使用 slice(1) 方法去掉第一个参数(即 context),获取剩余的参数。
  const args = [...args].slice(1)

  // 调用函数,并传递参数
  const result = context.fn(...args)

  // 删除临时添加的属性
  delete context.fn

  // 返回函数调用的结果
  return result
}

ok,现在你也可以发现,既然它们可以改变 this 的指向,那么就可以用它们来实现节流和防抖了,先来介绍一下节流防抖的概念

节流和防抖

节流(Throttle)和防抖(Debounce)是两种用于优化高频率函数调用的技术。它们都可以帮助减少函数的执行次数,从而提升性能,但它们的工作方式和适用场景有所不同。

节流

节流的作用是在规定的时间间隔内,保证一个函数最多执行一次。节流常用于处理那些频繁触发的事件,比如滚动、窗口调整大小、鼠标移动等。

我们来看一下具体实现,先来不需要 apply 的版本

// function 需要被节流的目标函数
// delay 约定的时间,以毫秒为单位。在这个时间间隔内,目标函数最多只能执行一次
function throttle(func, delay) {
  // 记录上一次目标函数被调用的时间戳,初始值为0。
  let lastCall = 0

  // 返回一个新的匿名函数,这个函数包裹了目标函数 func 并添加了节流逻辑。
  // ...args:使用 ES6 的扩展运算符(spread operator)来获取传给这个匿名函数的参数,并将这些参数传递给 func。
  return function (...args) {
    // 获取当前时间
    const now = new Date().getTime()

    // 检查时间间隔,如果当前时间减去上次触发时间小于约定的时间段,直接返回;也就是说,用户在这个时间段内触发了多次
    // 但是由于还在这个时间段内,所以我们只执行一次
    if (now - lastCall < delay) {
      return
    }

    lastCall = now

    // 将参数传递给目标函数,并调用它
    return func(...args)
  }
}

下面是一个完整的使用用例,当用户拖动窗口的时候,在约定的 200 毫秒内,控制台只打印一次

function onResize() {
  console.log('Window resized', new Date().getTime())
}
function throttle(func, delay) {
  let lastCall = 0
  return function () {
    const now = new Date().getTime()
    if (now - lastCall < delay) {
      return
    }
    lastCall = now
    return func()
  }
}

const throttledResize = throttle(onResize, 200)

window.addEventListener('resize', throttledResize)

接下来我们用 apply 函数实现一下节流

function throttle(func, delay) {
  let lastCall = 0
  return function (...args) {
    const now = new Date().getTime()
    if (now - lastCall < delay) {
      return
    }
    lastCall = now
    return func.apply(this, args)
  }
}

这两种实现方式,核心逻辑上面完全一样,区别只是在参数传递上

防抖

防抖(Debounce)是一种用于优化高频率函数调用的技术,它确保一个函数在事件结束后的指定时间内只执行一次。如果在这段时间内再次触发事件,则重新计时。防抖技术常用于处理那些需要在用户停止操作后执行的事件,比如输入框提示、表单验证等。

原理: 防抖通过设置一个延迟时间(如 200 毫秒),在事件触发后开始计时。如果在延迟时间内再次触发事件,则清除之前的计时器并重新开始计时。只有当延迟时间过去后没有再次触发事件,函数才会执行。

function debounce(func, wait) {
  let timeout
  return function (...args) {
    clearTimeout(timeout)

    // 设置一个新的计时器,在延迟时间 wait 之后调用目标函数 func。
    // 使用箭头函数(=>)确保 this 的值在回调中与外层函数一致。
    // func.apply(this, args):使用 apply 方法调用目标函数 func,并确保 this 上下文和参数 args 被正确传递。
    timeout = setTimeout(() => {
      func.apply(this, args)
    }, wait)
  }
}

示例:

function onInput() {
  console.log('Input event', new Date().toISOString())
}

const debouncedInput = debounce(onInput, 300)

const inputElement = document.querySelector('input')
inputElement.addEventListener('input', debouncedInput)

onInput 函数只有在用户停止输入 300 毫秒后才会执行。如果用户在 300 毫秒内继续输入,计时器将重新开始。

好了,来聊聊最近的感受吧

说实话,这段时间过得并不好,该从哪里说起呢,就从这篇文章来说起吧。其实今年我一直想写文章,不管是技术学习上的还是生活上的,但是我今年一直在找机会,找一个新的工作;所以我心想,等我找到一个工作之后,就开始写文章,因为我可以学习公司的业务,没准可以产出更好的文章。

但是我的希望落空了,年后我就一直在陆陆续续的投递简历,但是没什么回应,后来我开始学习 react,看了很多教程,也做了一些笔记,都存放在我的 obsidian 上了,上个月22号,我开始写一个react项目IMaker,我花了一周左右的时间去写,到现在我也在修改commit,到今天这个项目在GitHub上大概有 200star 了。

这是我第一次获得这么多的star,心中有些窃喜,我大概花了两周左右就到了100,于是我赶紧修改简历,加上这个新的项目,开始投递简历,第一周有两个面试,一家是web3,一家是小红书的外包,我感觉自己答的还不错,基本都回答上了,可是就是没有下文。

第二周,没有任何回应,我又开始有些焦虑了,第三周没有回应,我不知道是哪里出了问题,也许确实是我的业务能力和技术不太行。我开始怀疑自己,甚至有些抱怨,但后来我意识到,这样下去没有任何的用处,除了消耗自己,还是消耗自己。

前两天做了一个线上测试,有几道算法题没有写出来,不出意料,挂了,但是我并没有太过失落,至少我知道了自己的一些短板,我想在后面的时间里去练习算题题,虽然我真的很不喜欢算法题。

这几天我听到一些话语,大概意思就是,“你觉得自己不幸,可你没有跟那些真正不幸的人对比过”。

我想了想,确实是这样,我只不过是暂时找不到一份合适的工作,四肢健全,身体也没有什么太大的问题,已经十分幸运、十分幸运、十分幸运了。

我来到这个世上不是为了内耗自己,即使生活过得不怎样,可我保持对生活的乐观,不矢为一件幸事。

我人目前在北京,只有一年多的经验,想找一份前端的工作,技术栈是 vue3 和 react

这是我的简历

我的邮箱是:slince1030@gmail.com

如果您有工作机会,欢迎联系我

如果您有任何想问的,或者想跟聊聊,欢迎邮件联系我。