前端性能优化之—防抖与节流(2)

在上一篇中,我们了解了为什么要限制事件的频繁触发
以及如何做限制:debounce 防抖与throttle 节流。

今天重点讲讲节流的实现。

一、防抖与节流的区别

  • 防抖:触发高频函数事件后,n秒内函数只能执行一次,如果在n秒内这个事件再次被触发的话,那么会重新计算时间,可以理解为仅仅只会调用一次
  • 节流:高频事件触发,但在n秒内只会执行一次,所以节流会稀释函数的执行频率,当达到了一定的时间间隔就会执行一次,可以理解为是缩减执行频率

这么一对比,何为节流,是不是有点儿明白啦~

节流的原理:如果你持续触发事件,每隔一段时间,只执行一次事件。

根据首次是否执行以及结束后是否执行,效果有所不同,实现的方式也有所不同。
我们用 leading 代表首次是否执行,trailing 代表结束后是否再执行一次。

关于节流的实现,有两种主流的实现方式,一种是使用时间戳,一种是设置定时器

二、学会节流

  1. 使用时间戳

使用时间戳,当触发事件的时候,我们取出当前的时间戳,然后减去之前的时间戳(最一开始值设为 0 ),如果大于设置的时间周期,就执行函数,然后更新时间戳为当前的时间戳,如果小于,就不执行。

//第一版,使用时间戳
	function throttle(func, wait) {
    	var context, args;
    	var previous = 0;

    	return function() {
        	var now = +new Date();
        	context = this;
        	args = arguments;
        	if (now - previous > wait) {
            	func.apply(context, args);
            	previous = now;
        	}
    	}
	}
//例子依然是用讲 debounce 中的例子,如果你要使用:
	container.onmousemove = throttle(getUserAction, 1000);

一起来看看效果:当鼠标移入的时候,事件立刻执行,每过 1s 会执行一次,如果在 4.2s 停止触发,以后不会再执行事件。
前端性能优化之—防抖与节流(2)_第1张图片

  1. 使用定时器

当触发事件的时候,我们设置一个定时器,再触发事件的时候,如果定时器存在,就不执行,直到定时器执行,然后执行函数,清空定时器,这样就可以设置下个定时器。

//第二版,使用定时器
	function throttle(func, wait) {
    	var timeout;
    	var previous = 0;

    	return function() {
        	context = this;
        	args = arguments;
        	if (!timeout) {
            	timeout = setTimeout(function(){
                	timeout = null;
                	func.apply(context, args)
            	}, wait)
	        }

    	}
	}
	
//为了让效果更加明显,我们设置 wait 的时间为 3s
	container.onmousemove = throttle(getUserAction, 3000);

效果演示如下:当鼠标移入的时候,事件不会立刻执行,晃了 3s 后终于执行了一次,此后每 3s 执行一次,当数字显示为 3 的时候,立刻移出鼠标,相当于大约 9.2s 的时候停止触发,但是依然会在第 12s 的时候执行一次事件。
前端性能优化之—防抖与节流(2)_第2张图片

所以比较两个方法:

  • 第一种事件会立刻执行,第二种事件会在 n 秒后第一次执行
  • 第一种事件停止触发后没有办法再执行事件,第二种事件停止触发后依然会再执行一次事件
  1. 双剑合璧

那我们想要一个什么样的呢?有人就说了:我想要一个有头有尾的!就是鼠标移入能立刻执行,停止触发的时候还能再执行一次!所以我们综合两者的优势,然后双剑合璧,写一版代码:

//第三版,结合时间戳与定时器
function throttle(func, wait) {
    var timeout, context, args, result;
    var previous = 0;

    var later = function() {
        previous = +new Date();
        timeout = null;
        func.apply(context, args)
    };

    var throttled = function() {
        var now = +new Date();
        //下次触发 func 剩余的时间
        var remaining = wait - (now - previous);
        context = this;
        args = arguments;
         // 如果没有剩余的时间了或者你改了系统时间
        if (remaining <= 0 || remaining > wait) {
            if (timeout) {
                clearTimeout(timeout);
                timeout = null;
            }
            previous = now;
            func.apply(context, args);
        } else if (!timeout) {
            timeout = setTimeout(later, remaining);
        }
    };
    return throttled;
}

效果如下 :鼠标移入,事件立刻执行,晃了 3s,事件再一次执行,当数字变成 3 的时候,也就是 6s 后,我们立刻移出鼠标,停止触发事件,9s 的时候,依然会再执行一次事件。前端性能优化之—防抖与节流(2)_第3张图片

  1. 优化

但是我有时也希望无头有尾,或者有头无尾,这个咋办?

那我们设置个 options 作为第三个参数,然后根据传的值判断到底哪种效果,我们约定:

leading:false 表示禁用第一次执行
trailing:false 表示禁用停止触发的回调

我们来改一下代码:

//第四版,优化
function throttle(func, wait, options) {
    var timeout, context, args, result;
    var previous = 0;
    if (!options) options = {};

    var later = function() {
        previous = options.leading === false ? 0 : new Date().getTime();
        timeout = null;
        func.apply(context, args);
        if (!timeout) context = args = null;
    };

    var throttled = function() {
        var now = new Date().getTime();
        if (!previous && options.leading === false) previous = now;
        var remaining = wait - (now - previous);
        context = this;
        args = arguments;
        if (remaining <= 0 || remaining > wait) {
            if (timeout) {
                clearTimeout(timeout);
                timeout = null;
            }
            previous = now;
            func.apply(context, args);
            if (!timeout) context = args = null;
        } else if (!timeout && options.trailing !== false) {
            timeout = setTimeout(later, remaining);
        }
    };
    return throttled;
}
  1. 取消

和防抖的取消一样,节流中我们也加一个取消

//第五版,添加取消
var count = 1;
var container = document.getElementById('container');

function getUserAction() {
    container.innerHTML = count++;
};

var setUseAction = throttle(getUserAction, 10000);

container.onmousemove = setUseAction

document.getElementById("button").addEventListener('click', function(){
    setUseAction.cancel();
})

function throttle(func, wait, options) {
    var timeout, context, args, result;
    var previous = 0;
    if (!options) options = {};

    var later = function() {
        previous = options.leading === false ? 0 : new Date().getTime();
        timeout = null;
        func.apply(context, args);
        if (!timeout) context = args = null;
    };

    var throttled = function() {
        var now = new Date().getTime();
        if (!previous && options.leading === false) previous = now;
        var remaining = wait - (now - previous);
        context = this;
        args = arguments;
        if (remaining <= 0 || remaining > wait) {
            if (timeout) {
                clearTimeout(timeout);
                timeout = null;
            }
            previous = now;
            func.apply(context, args);
            if (!timeout) context = args = null;
        } else if (!timeout && options.trailing !== false) {
            timeout = setTimeout(later, remaining);
        }
    };

    throttled.cancel = function() {
        clearTimeout(timeout);
        previous = 0;
        timeout = null;
    };

    return throttled;
}
  1. 注意: 我们要注意 underscore 的实现中有这样一个问题:
    那就是 leading:falsetrailing:false 不能同时设置。

如果同时设置的话,比如当你将鼠标移出的时候,因为 trailing 设置为 false,停止触发的时候不会设置定时器,所以只要再过了设置的时间,再移入的话,就会立刻执行,就违反了 leading: false,bug 就出来了

所以,这个 throttle 只有三种用法

//使用
container.onmousemove = throttle(getUserAction, 1000);
container.onmousemove = throttle(getUserAction, 1000, {
    leading: false
});
container.onmousemove = throttle(getUserAction, 1000, {
    trailing: false
});

终于,我们已经完整实现了一个 underscore 中的 debounce 函数,恭喜,撒花!

本文学习自这位大佬,感谢!

你可能感兴趣的:(杂宝杂藏)