这几天复习到了两个概念,一个是节流,一个是防抖,他们都是可以用于浏览器性能优化。
不过在说明节流和防抖的概念之前,先说一下 call
、apply
、bind
这三个函数
call、apply、bind
这三个函数都是 JS
中函数的方法,是用来改变函数调用时的 this
绑定并传递参数,换句话来说,就是改变 this
的指向。它允许你在不同的上下文中调用一个函数,这些方法都是 Function
原型,所以所有函数都可以直接调用它们。
下面来分别看看它们仨
call
call
方法调用一个函数,并指定 this
的值和单个参数列表。
func.call(thisArg, arg1, arg2, ...)
thisArg
:调用函数时this
的值。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])
thisArg
:调用函数时this
的值。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, ...)
thisArg
:调用新函数时this
的值。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
和apply
:都用于立即调用函数,并改变this
的值。区别在于call
接受参数列表,而apply
接受参数数组。bind
:用于创建一个新函数,该函数在调用时将this
绑定到提供的值,并可以预设部分参数。新函数不会立即执行,而是可以在稍后调用。
使用场景
call
:适用于函数立即调用且参数数量固定的情况。apply
:适用于函数立即调用且参数以数组形式传递的情况,特别是参数数量不固定时。bind
:适用于创建一个新的函数,并在稍后某个时刻调用该函数,同时预设this
绑定和部分参数。
接下来我们来自己实现一个 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)
}
}
这两种实现方式,核心逻辑上面完全一样,区别只是在参数传递上
- 使用 apply 方法更适合已经有参数数组的情况,并且需要显式指定 this 上下文。
- 使用扩展运算符 …args 更简洁,适合直接处理参数列表且不需要显式指定 this 上下文的情况。
防抖
防抖(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
如果您有工作机会,欢迎联系我
如果您有任何想问的,或者想跟聊聊,欢迎邮件联系我。