【JS基础】通俗易懂的讲清楚去抖/防抖、节流。外加手写深度比较

去抖/防抖

去抖也叫防抖,为了照顾JS初学者的理解和记忆,我就简单的说明一下。

我们生活中很多出现抖动的现象,都是没有规律的,例如人的发抖、树叶在风中的抖动、海浪的摆动等。那么去抖,防抖这个概念能够见词答意的看出,就是为了抵消无规律的抖动。怎么个抵消法呢?就是在一段固定的时间内,抖动现象停止了,那么就立即发出信号。

抖动停止发出信号,抖动停止发出信号… 是不是,好像就变成了一种有规律的现象了。

好,我举个应用的例子你就更加明白了,页面上有个搜索框,用户在上面输入关键词,输入完后触发input事件,这时事件的处理逻辑就是发送异步请求去获取关键词的搜索结果。此时,用户只要一输入一个字,立马就会发送请求,当用户连续的输入时,就会连续的发送多个请求,会出现什么问题?

  • 异步请求的相应时间是无法确定的,可能第3个请求的数据量比较大返回的时间久,而最后一个请求数据量小一下子就返回了,那么最后一个请求的数据就会在第3个请求回来之前获取到,而第3个请求的结果最后才获取到,也就是说,最终显示的搜索结果变成了第3个请求的结果。蛋疼不?
  • 连续短时间内无规律的发送多个异步请求,对前端性能非常不友好(页面卡顿),对服务器也不友好(几万个用户同时这么去搜索请求服务器遭不住啊)

这时候就用到了防抖机制,我们在用户停止输入1s后,再去调用异步请求,是不是就大大减少了请求的数量。例如用户连续快速输入“广东省深圳市”,假如没有防抖机制,输入完后一共无规律的发送了6个接口,有了防抖机制,只发送了1次,就是用户输入完"市"停止1s的时候。

代码简单实现:

function debounce(fn, delay = 500) {
    // timer 是闭包中的
    let timer = null

    return function () {
        if (timer) {
            clearTimeout(timer)
        }
        timer = setTimeout(() => {
            fn.apply(this, arguments) // 把默认的event入参指向要执行的函数
            timer = null
        }, delay)
    }
}

input1.addEventListener('keyup', debounce(function (e) {
    console.log(e.target)
    console.log(input1.value)
}, 600))

节流

节流,节流,节约流水(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


深度比较

就是比较两个变量是不是内容一样的,主要是考虑多层对象或者数组。

// 判断是否是对象或数组
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
}

版权声明:本文为pagnzong原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。