节流(Throttling):是指在一段时间内,无论事件被触发多少次,都只执行一次处理函数。(可以理解为攻速,屏幕点烂了也还是按照一定攻速射击)
应用场景举例:鼠标移动、页面滚动;
节流的四种实现方式(其实是两种):
- 定时器不闭包实现
- 定时器闭包实现
- 时间戳不闭包实现
- 时间戳闭包实现
实现思路:在节流被触发时,判断触发频率是否超过指定时间间隔,如果超过,则执行处理函数,并更新上一次执行处理函数的时间戳;如果没有超过,则忽略该次触发事件。
示例如下:
// 定时器不闭包实现
let _time = null;
function throttle(func, delay:1000) {
if (_time) return
_timer = setTimeout(() => {
clearTimeout(_timer)
_timer = null
func;
}, delay)
}
// 定时器闭包实现
function throttle(func, delay:1000) {
let _time = null;
return function () {
let context = this;
let args = arguments;
if (_time) return
_timer = setTimeout(() => {
clearTimeout(_timer)
_timer = null
func.apply(context, args);
}, delay)
}
}
// 时间戳不闭包实现
let lastTime = 0;
let now = Date.now();
function throttle(func, delay:1000) {
if (now - lastTime >= delay) {
lastTime = now;
func;
}
}
// 时间戳闭包实现
function throttle(func, delay:1000) {
let lastTime = 0;
return function () {
let context = this;
let args = arguments;
let now = Date.now();
if (now - lastTime >= delay) {
lastTime = now;
func.apply(context, args);
}
}
}
防抖(Debouncing):是指在一个事件被频繁触发时,延迟执行处理函数,在最后一次事件触发之后一定时间内,如果没有新的事件触发,则执行该函数。(可以理解为回城读条操作,如果打断了又要重新读条-记住回城的时候不能抖动不然会打断就行)
应用场景举例:搜索框、窗口大小调整;
防抖的两种实现方式(其实是一种):
- 定时器不闭包实现
- 定时器闭包实现
实现思路:在函数被触发时,设置一个定时器,如果在定时器的时间范围内该函数再次被触发,则清除定时器并重新设置一个新的定时器,直到定时器时间范围内没有新的事件触发,最后执行该函数。
示例如下:
// 定时器不闭包实现
let timer = null;
function debounce(func, delay:1000) {
clearTimeout(timer);
timer = setTimeout(() => {
func;
}, delay);
}
// 定时器闭包实现
function debounce(func, delay:1000) {
let timer = null;
return function () {
let context = this;
let args = arguments;
clearTimeout(timer);
timer = setTimeout(function () {
func.apply(context, args);
}, delay);
}
}
tip*提升:
-
在
addEventListener监听器中使用防抖和节流的注意事项- 要注意监听触发的回调
fun和fun()的区别(有无括号),前者是触发函数体,后者是触发函数return的事件(这里就要用闭包了),举例如下:
function test () { console.log("----fun") return function () { console.log("++++fun()") } } window.addEventListener("scroll", test); // 这里打印----fun window.addEventListener("scroll", test()); // 这里打印++++fun() - 要注意监听触发的回调
-
在vue项目中怎么将防抖和节流做成元素的自定义指令(v-throttle),如下:
// 自定义指令-节流
<button v-throttle="handleClick">Click me</button>
// 定义 v-throttle 指令
Vue.directive('throttle', {
bind: function (el, binding) {
let delay = binding || 500; // 获取指令的参数,默认为 500ms
let lastTime = 0; // 记录上一次执行的时间
el.addEventListener('click', function () {
let now = Date.now(); // 获取当前时间
if (now - lastTime >= delay) { // 判断当前时间与上一次执行时间的差是否超过指定时间间隔
binding(); // 执行指令的绑定值,即节流处理函数
lastTime = now; // 更新上一次执行时间
}
})
}
})
// 自定义指令-防抖
<input type="text" v-debounce="handleInput">
// 定义 v-debounce 指令
Vue.directive('debounce', {
bind: function (el, binding) {
let delay = binding || 500; // 获取指令的参数,默认为 500ms
let timer = null; // 定义定时器
el.addEventListener('input', function () {
clearTimeout(timer); // 清除上一次的定时器
timer = setTimeout(() => {
binding(); // 执行指令的绑定值,即防抖处理函数
}, delay);
})
}
})
我们要在项目中使用的话,按照如下操作来注册:
// 在utils文件夹下创建一个directiveAPI.js文件
export const throttle = {
install(Vue) {
// 定义 v-throttle 指令,上面的复制下来
XXXXX......
}
}
export const debounce = {
install(Vue) {
// 定义 v-debounce 指令,上面的复制下来
XXXXX......
}
}
//注册方法
export default function directivesInstall(Vue) {
Vue.use(throttle)
Vue.use(debounce)
}
// 然后再src/main.js下初始化我们需要注册的方法`directivesInstall`
import directivesInstall from '@/utils/directiveMed' // 自定义指令
Vue.use(directivesInstall) //注册指令
tip*提升-2:
首先先要说一下为什么用get/set来做防抖和节流
答:
- 应用场景:当我们在处理事件、获取元素时就需要用到
get/set,所以需要再这些场景下进行防抖和节流就需要用到。 - 防抖和节流的条件:普通的防抖和节流是在组件值
updata的时候进行,但是用get/set可以在组件挂载的时候进行. - 实现方式:跟tip1中v-on不同
- Vue2中让对象实现响应式的防抖和节流(proxy或Object.defineProperty)
// 方法定义-防抖
function useDebouncedRef (data, delay = 300) {
let timer = null
const value = {value: data} // ?????
// 创建 proxy 实例
const proxy = new Proxy(value, {
get(target, property) {
// 返回当前值
return target[property]
},
// target:目标, property:属性, newValue 值, receiver:接收者
set(target, property, newValue, receiver) {
if(timer != null){
clearTimeout(timer)
timer = null
}
timer = setTimeout(() => {
// 修改值
target[property] = newValue
}, delay)
return true;
}
})
}
<script>
const text = useDebouncedRef(0, 300) // 这里进行声明
</script>
<template>
<input v-model="text" />
</template>
- Vue3中让对象实现响应式的防抖和节流(customRef)
定义:创建一个自定义的 ref,显式声明对其依赖追踪和更新触发的控制方式。
使用:customRef() 预期接收一个工厂函数作为参数,这个工厂函数接受 track(收集依赖)和 trigger(派发更新) 两个函数作为参数,并返回一个带有 get 和 set 方法的对象。
意义:为了在组件更新时减少性能瓶颈,可以有效地减少组件更新的频率,提高页面性能。此外,customRef 还可以用于处理事件、获取元素等场景
// 方法定义-防抖
function useDebouncedRef(value, delay = 200) {
let timeout
return customRef((track, trigger) => {
return {
get() {
track() // 收集依赖
return value
},
// newValue 值
set(newValue) {
clearTimeout(timeout)
timeout = setTimeout(() => {
value = newValue
trigger() // 派发更新
}, delay)
}
}
})
}
<script setup>
const text = useDebouncedRef('hello') // 这里进行声明
</script>
<template>
<input v-model="text" />
</template>