去抖/防抖
去抖也叫防抖,为了照顾JS初学者的理解和记忆,我就简单的说明一下。
我们生活中很多出现抖动的现象,都是没有规律的,例如人的发抖、树叶在风中的抖动、海浪的摆动等。那么去抖,防抖这个概念能够见词答意的看出,就是为了抵消无规律的抖动。怎么个抵消法呢?就是在一段固定的时间内,抖动现象停止了,那么就立即发出信号。
抖动停止发出信号,抖动停止发出信号… 是不是,好像就变成了一种有规律的现象了。
好,我举个应用的例子你就更加明白了,页面上有个搜索框,用户在上面输入关键词,输入完后触发input事件,这时事件的处理逻辑就是发送异步请求去获取关键词的搜索结果。此时,用户只要一输入一个字,立马就会发送请求,当用户连续的输入时,就会连续的发送多个请求,会出现什么问题?
- 异步请求的相应时间是无法确定的,可能第3个请求的数据量比较大返回的时间久,而最后一个请求数据量小一下子就返回了,那么最后一个请求的数据就会在第3个请求回来之前获取到,而第3个请求的结果最后才获取到,也就是说,最终显示的搜索结果变成了第3个请求的结果。蛋疼不?
- 连续短时间内无规律的发送多个异步请求,对前端性能非常不友好(页面卡顿),对服务器也不友好(几万个用户同时这么去搜索请求服务器遭不住啊)
这时候就用到了防抖机制,我们在用户停止输入1s后,再去调用异步请求,是不是就大大减少了请求的数量。例如用户连续快速输入“广东省深圳市”,假如没有防抖机制,输入完后一共无规律的发送了6个接口,有了防抖机制,只发送了1次,就是用户输入完"市"停止1s的时候。
代码简单实现:
function debounce(fn, delay = 500) {
let timer = null;
return function() {
if (timer) {
clearTimeout(timer);
}
timer = setTimeout(() => {
fn(...arguments);
timer = null;
}, delay);
};
}
input1.addEventListener(
"keyup",
debounce(function(event) {
console.log(event.target);
console.log(input1.value);
}, 600)
);
下面看为什么这么写
思路解析
首先咱们要设计输入和输出,输入的话我们必定要传入需要防抖的函数本体,然后第二个参数咱们可以设置防抖的时长。输出我们就返回一个包了一层防抖机制的新函数。
function debounce(fn, delay = 500) {
return function() {
};
}
防抖机制的设计,只要涉及到了计数咱们只考虑setTimeOut,因为setInterVal当页面卡顿时计数也会受到影响。
function debounce(fn, delay = 500) {
return function() {
let timer = setTimeout(() => {
fn();
timer = null;
}, delay);
};
}
使用的时候报错了,因为console.log(event.target)
中的event为undefined。这是因为咱们的监听事件的默认参数并没有传入到fn里。所以我们要在返回的函数中,传入默认的参数。
function debounce(fn, delay = 500) {
let timer = null;
console.log('arguments', arguments) // 这里是debounce的默认参数
return function() {
if (timer) {
clearTimeout(timer);
}
timer = setTimeout(() => {
fn(...arguments); // 这里是return function的默认参数,也就是keyup传进来的,可能一开始会有点难理解
timer = null;
}, delay);
};
}
除了优化【查】的操作,在【增删改】的接口调用时,如果以按钮之类的形式去触发,也一定要考虑到去抖的操作,当然这种情况不一定要使用去抖的写法,用个局部loading也是不错的选择。
节流
节流,节流,节约流水(doge),哈哈,JS初学者完全可以这样子去理解。当我们去开水龙头的时候,水是不是就一直哗啦啦的流出,在一些场景下很浪费水资源,这时我们可以拧紧调节水龙头,让水滴每0.3s滴一滴,是不是就很节约流水啦。
说白了节流是当频繁触发时,保持一定的频率触发。
举例子吧,例如js中的drag、scroll、mousemove事件,就拿mousemove事件说明,当我们移动鼠标想获取实时x、y坐标处理业务逻辑的时候,mousemove事件被浏览器频繁的触发(像流水一样一发不可收拾)会有什么坏处?
- 大量的x、y数据的处理并不是我们所需要的,多余的处理只会增加浏览器资源开销,这种情况出现多了会有浏览器卡顿的现象。
这时候,就需要保持一定的频率的获取x、y坐标,例如0.5s获取一次。这样能够大大减轻性能负担,又不失去时效性。
function throttle(fn, delay = 100) {
let timer = null
return function () {
if (timer) {
return
}
timer = setTimeout(() => {
fn.apply(this, arguments) // 把默认的event入参指向要执行的函数
timer = null
}, delay)
}
}
div1.addEventListener('drag', throttle(function (e) {
console.log(e.offsetX, e.offsetY)
}))
对比防抖可以看出,节流关注的是过程,而防抖关注的是结果。
注意!工作中尽量不要自己造轮子去使用防抖和节流,应该使用成熟的工具库,例如loadsh
两者在vue中结合计算属性使用
个人认为在vue中,把防抖和节流结合计算属性比较好用。
computed: {
// 防抖的
debounceBtnClick(){
return this.debounce(this.btnClick, 300) // btnClick就是method中真正要触发的函数,在模板中的方法里绑定该计算属性即可
},
// 节流的未来补充
}
深度比较
就是比较两个变量是不是内容一样的,主要是考虑多层对象或者数组。
// 判断是否是对象或数组
function isObject(obj) {
return typeof obj === 'object' && obj !== null
}
// 深度比较
function isEqual(obj1, obj2) {
// 1 如果是值类型的话就直接比较
if (!isObject(obj1) || !isObject(obj2)) {
return obj1 === obj2
}
// 2 如果传了同一个变量进来直接返回true
if (obj1 === obj2) {
return true
}
// 3 两个都是对象或数组,而且不相等
// a 为了性能,可以先比较属性个数或数组大小
const obj1Keys = Object.keys(obj1)
const obj2Keys = Object.keys(obj2)
if (obj1Keys.length !== obj2Keys.length) {
return false
}
// b 以obj1为基准,和obj2递归比较
for (let key in obj1) {
const res = isEqual(obj1[key], obj2[key])
if (!res) { // 不一样的直接返回false
return false
}
}
// 相等就返回true
return true
}