防抖与节流

目录

防抖与节流

防抖

短时间内多次触发同一函数,只执行第一次/最后一次。比如说:一只小猫冷的发抖,稍用力按住它,它就不抖了。可以在它还没抖的时候按住,也可以在它抖了很久之后按住

普通防抖

/**
 * 普通实现
 * 函数防抖[函数高频率被调用只执行第一次/最后一次]
 * @param {Function} func 需要防抖的函数
 * @param {Number} [duration=1000] 防抖时长[多长时间内的点击需要防抖]
 */
function debounce (func, duration = 1000) {
    let timer = null;
    return function (...args) {
        /*if (timer!== null)*/ clearTimeout(timer);
        timer = setTimeout(() => {
            clearTimeout(timer);
        	func.apply(this, args);
        }, delay);
    }
}

如果要给防抖函数绑定this,使用方式如下

绑定this

function mClick() {
    console.log('我被点名啦!');
}

debounce(mClick).bind(this);

有配置项的防抖

/**
 * 函数防抖
 * @param {Function} func 需要防抖的函数
 * @param {Boolean} [first=true] 是否首次防抖
 * @param {Number} [duration=1000] 防抖持续时长
 * @returns 
 */
function debounce(func, first = true, duration = 1000){
    // 声明一个空的定时器id,为防抖做准备
	let timer = null;
	// 返回函数是因为这个防抖函数可能会被绑定/赋值给其他函数使用
	return function (...args) {
		first && !timer && func.apply(this, args);
		// 不用判断timer是否存在,传入一个错误的 ID 给 clearTimeout()不会有任何影响;也不会抛出异常。
		clearTimeout(timer);
		timer = setTimeout(() => {
            clearTimeout(timer);
			// 非首次防抖则调用这个函数
			!first && func.apply(this, args);
			// 为下一次防抖做准备
			timer = null;
		}, duration);
	};
}

可取消的防抖

/**
 * 函数防抖
 * @param {Function} func 需要防抖的函数
 * @param {Boolean} [first=true] 是否首次防抖
 * @param {Number} [duration=1000] 防抖持续时长
 * @returns 
 */
function debounce(func, first = true, duration = 1000){
    // 声明一个空的定时器id,为防抖做准备
	let timer = null;
    let debounced = function (...args) {
		first && !timer && func.apply(this, args);
		// 不用判断timer是否存在,传入一个错误的 ID 给 clearTimeout()不会有任何影响;也不会抛出异常。
		clearTimeout(timer);
		timer = setTimeout(() => {
            clearTimeout(timer);
			// 非首次防抖则调用这个函数
			!first && func.apply(this, args);
			// 为下一次防抖做准备
			timer = null;
		}, duration);
	};
    debounced.cancel = function () {
        clearTimeout(timer);
        // 为下一次防抖做准备
        timer = null;
    }
	// 返回函数是因为这个防抖函数可能会被绑定/赋值给其他函数使用
	return debounced;
}

节流

一段时间内只允许函数执行一次

使用场景:滚动条滚动,过程中速度可能很快,但是肉眼捕捉不到,所以那些肉眼捕捉不到的函数调用是不必要的,故而节流,节省性能。

时间戳节流

首次执行,duration期间内尾次不执行

会在第一次触发事件时立即执行[给事件绑定函数与真正触发事件的间隔一般大于duration],以后每过duration时长后,函数可以被再次触发执行。

/**
 * 函数节流
 * @param {Function} func 需要节流的函数
 * @param {Number} [duration=1000] 节流持续的时长
 */
function throttle(func, duration = 1000) {
    let prev = 0;
    return function (...args) {
        const now = Date.now();
        // 两次函数被调用的时长大于节流时长
        if (now - prev > duration) {
            func.apply(this, args);
            prev = Date.now();
        }
    }
}

绑定this

function mScroll () {
    console.log('我滑滑梯啦!');
}
throttle(mScroll).bind(this);

定时器节流

第一次触发不会执行,而是在duration时长后执行,当最后一次停止触发后,还会再执行一次函数。

/**
 * 函数节流
 * @param {Function} func 需要被节流的函数
 * @param {Number} [duration=1000] 节流持续时长
 */
function throttle(func, duration = 1000) {
    let timer = null;
    return function (...args) {
        // 定时器未执行结束,函数再次被调用不生效
        if (!timer) {
            timer = setTimeout(() => {
                clearTimeout(timer);
                func.apply(this, args);
                timer = null;
            }, duration);
        }
    }
}

时间戳 + 定时器的节流

首、尾次均执行。[两者交替使用,第一次是时间戳,第二次是定时器,第三次是时间戳,第四次是定时器……,理想状态下如此,但碍于定时器的消息队列处理等影响,有时可能不是这样。]

/**
 * 函数节流
 * @param {Function} func 需要被节流的函数
 * @param {Number} [duration=1000] 节流持续时长
 */
function throttle (func, duration = 1000) {
    let timer, prev = 0;
    return function (...args) {
        const now = Date.now();
        // 解决第一次不执行
        if (now - prev > duration) {
            // 某个定时器执行结束,清除定时器
            if (timer){
                clearTimeout(timer);
                // 如果定时器超过了duration时长仍然没有执行函数[被其他事情影响],则取消这个定时器,立即执行函数
           		timer = null;
            }
            func.apply(this, args);
            prev = Date.now();
        } else if (!timer) { // 解决最后一次不执行
            // 两次调用时长小于duration时使用
            timer = setTimeout(() => {
                prev = Date.now();
                func.apply(this, args);
                timer = null;
            }, duration);
        }
    };
}

有配置项的节流

/**
 * 函数节流
 * @param {Function} func 需要被节流的函数
 * @param {Object} option 节流配置项
 * option = { 
 * 		leading: false, // 禁止第一次执行
 * 		trailing: false // 禁止最后一次执行
 *	}
 *		leading和trailing不能同时为false[同为false,节流失效]
 * @param {Number} [duration=1000] 节流持续时长
 */
function throttle (func, option, duration = 1000) {
    !option && (option = { leading: true, trailing: true });
    let timer, prev = 0;
    return function (...args) {
        const now = Date.now();
        if (!(option.leading || prev)) {
            prev = now;
        }
        // 解决第一次不执行
        if (now - prev > duration) {
            // 某个定时器执行结束,清除定时器
            if (timer){
                clearTimeout(timer);
                // 如果定时器超过了duration时长仍然没有执行函数[被其他事情影响],则取消这个定时器,立即执行函数
           		timer = null;
            }
            func.apply(this, args);
            prev = Date.now();
        } else if (!timer && option.trailing) { // 解决最后一次不执行
            // 两次调用时长小于duration时使用
            timer = setTimeout(() => {
                clearTimeout(timer);
                prev = Date.now();
                func.apply(this, args);
                timer = null;
            }, duration);
        }
    };
}

可取消的节流

/**
 * 函数节流
 * @param {Function} func 需要被节流的函数
 * @param {Object} option 节流配置项
 * option = { 
 * 		leading: false, // 禁止第一次执行
 * 		trailing: false // 禁止最后一次执行
 *	}
 *		leading和trailing不能同时为false[同为false,节流失效]
 * @param {Number} [duration=1000] 节流持续时长
 */
function throttle (func, option, duration = 1000) {
    !option && (option = { leading: true, trailing: true });
    let timer, prev = 0;
    
    let trottled = function (...args) {
        const now = Date.now();
        if (!(option.leading || prev)) {
            prev = now;
        }
        // 解决第一次不执行
        if (now - prev > duration) {
            // 某个定时器执行结束,清除定时器
            if (timer){
                clearTimeout(timer);
                // 如果定时器超过了duration时长仍然没有执行函数[被其他事情影响],则取消这个定时器,立即执行函数
           		timer = null;
            }
            func.apply(this, args);
            prev = Date.now();
        } else if (!timer && option.trailing) { // 解决最后一次不执行
            // 两次调用时长小于duration时使用
            timer = setTimeout(() => {
                clearTimeout(timer);
                prev = Date.now();
                func.apply(this, args);
                timer = null;
            }, duration);
        }
    };
    
    trottled.cancel = function () {
        clearTimeout(timer);
        timer = null;
        prev = 0;
    }
    return trottled;
}

window.requestAnimationFrame节流

节流时机不可控[函数不知道什么时候就被调用了,因为不知道什么时间浏览器会重绘]

/**
 * 函数节流
 * @param {Function} func 需要节流的函数
 */
function throttle(func) {
    let lock = false;
    return function (...args) {
        !lock && window.requestAnimationFrame(() => {
            func.apply(this, args);
            lock = false;
        });
        lock = true;
    }
}

参考

JS系列之事件节流(Debounce)

[JavaScript] 节流(throttle)-防抖(debounce) 不懵圈指北

JS手写系列之防抖节流

JS事件节流(Throttle)

使用 requestAnimationFrame 替代 throttle 优化页面性能

上一篇:潭州课堂25班:Ph201805201 WEB 之 页面编写 第二课 (课堂笔记)


下一篇:《TCP/IP详解卷1:协议》第6章 ICMP:Internet控制报文协议-读书笔记