zoukankan      html  css  js  c++  java
  • 理解JavaScript中的去抖函数

    何为去抖函数?在学习JavaScript去抖函数之前我们需要先弄明白这个概念。很多人都会把去抖跟节流两个概念弄混,但是这两个概念其实是很好理解的。

    去抖函数(Debounce Function),是一个可以限制指定函数触发频率的函数。我们可以理解为连续调用同一个函数多次,只得到执行该函数一次的结果;但是隔一段时间再次调用时,又可以重新获得新的结果,具体这段时间有多长取决于我们的设置。这种函数的应用场景有哪些呢?

    比如我们写一个DOM事件监听函数,

    window.onscroll = function(){
        console.log('Got it!');
    }
    

      

    现在当我们滑动鼠标滚轮的时候,我们就可以看到事件被触发了。但是我们可以发现在我们滚动鼠标滚轮的时候,我们的控制台在不断的打印消息,因为window的scroll事件被我们不断的触发了。

    在当前场景下,可能这是一个无伤大雅的行为,但是可以预见到,当我们的事件监听函数(Event Handler)涉及到一些复杂的操作时(比如Ajax请求、DOM渲染、大量数据计算),会对计算机性能产生多大影响;在一些比较老旧的机型或者较低版本的浏览器(尤其IE)中,很可能会导致死机情况的出现。所以这个时候我们就要想办法,在指定时间段内,只执行一定次数的事件处理函数。

    理解去抖函数

    说了一些概念和应用场景,但是还是很拗口,到底什么是去抖函数?

    我们可以通过如下实例来理解:

    假设有以下代码:

    var debounce = function(callback, delay, immediate){
        var timeout, result;
        return function(){
            var callNow;
            if(timeout)
                clearTimeout(timeout);
            callNow = !timeout && immediate;
            if(callNow) {
                result = callback.apply(this, Array.prototype.slice.call(arguments, 0));
                timeout = {};
            }
            else {
                timeout = setTimeout(()=>{
                    callback.apply(this, Array.prototype.slice.call(arguments, 0));
                }, delay);
            }
        };
    };
    var s = debounce(()=>{
        console.log('yes...');
    }, 2000);
    window.onscroll = s;
    

      

    debounce函数就是我自己实现的一个简单的去抖函数,我们可以通过这段代码进行实验。

    步骤如下:

    • 复制以上代码,打开浏览器,打开控制台(F12),然后粘贴代码并回车执行。
    • 连续不断的滚动鼠标,查看控制台有无输出。
    • 停止滚动鼠标,2s之内再次滚动鼠标,查看是否有输出。
    • 连续滚动之后停止2s以上,查看是否有输出。

    通过以上步骤,我们可以发现当我们连续滚动鼠标时,控制台没有消息被打印出来,停止2s以内并再次滚动时,也没有消息输出;但是当我们停止的时间超过2s时,我们可以看到控制台有消息输出。

    这就是去抖函数。在连续的触发中(无论时长),只能得到触发一次的效果。在指定时间长度内连续触发,最多只能得到一次触发的效果。

    underscore的实现

    underscore源码如下(附代码注释):

    // Returns a function, that, as long as it continues to be invoked, will not
    // be triggered. The function will be called after it stops being called for
    // N milliseconds. If `immediate` is passed, trigger the function on the
    // leading edge, instead of the trailing.
    //去抖函数,传入的函数在wait时间之后(或之前)执行,并且只会被执行一次。
    //如果immediate传递为true,那么在函数被传递时就立即调用。
    //实现原理:涉及到异步JavaScript,多次调用_.debounce返回的函数,会一次性执行完,但是每次调用
    //该函数又会清空上一次的TimeoutID,所以实际上只执行了最后一个setTimeout的内容。
    _.debounce = function (func, wait, immediate) {
    	var timeout, result;
    
    	var later = function (context, args) {
    		timeout = null;
    		//如果没有传递args参数,那么func不执行。
    		if (args) result = func.apply(context, args);
    	};
    
    	//被返回的函数,该函数只会被调用一次。
    	var debounced = restArgs(function (args) {
    		//这行代码的作用是清除上一次的TimeoutID,
    		//使得如果有多次调用该函数的场景时,只执行最后一次调用的延时。
    		if (timeout) clearTimeout(timeout);
    		if (immediate) {
    			////如果传递了immediate并且timeout为空,那么就立即调用func,否则不立即调用。
    			var callNow = !timeout;
    			//下面这行代码,later函数内部的func函数注定不会被执行,因为没有给later传递参数。
    			//它的作用是确保返回了一个timeout。
    			timeout = setTimeout(later, wait);
    			if (callNow) result = func.apply(this, args);
    		} else {
    			//如果没有传递immediate,那么就使用_.delay函数延时执行later。
    			timeout = _.delay(later, wait, this, args);
    		}
    
    		return result;
    	});
    
    	//该函数用于取消当前去抖效果。
    	debounced.cancel = function () {
    		clearTimeout(timeout);
    		timeout = null;
    	};
    
    	return debounced;
    };
    

      

    可以看到underscore使用了闭包的方法,定义了两个私有属性:timeout和result,以及两个私有方法later和debounced。最终会返回debounced作为处理之后的函数。timeout用于接受并存储setTimeout返回的TimeoutID,result用于执行用户传入的func函数的执行结果,later方法用于执行传入的func函数。

    实现原理

    利用了JavaScript的异步执行机制,JavaScript会优先执行完所有的同步代码,然后去事件队列中执行所有的异步任务。

    当我们不断的触发debounced函数时,它会不断的clearTimeout(timeout),然后再重新设置新的timeout,所以实际上在我们的同步代码执行完之前,每次调用debounced函数都会重置timeout。所以异步事件队列中的异步任务会不断刷新,直到最后一个debounced函数执行完。只有最后一个debounced函数设置的later异步任务会在同步代码执行之后被执行。

    所以当我们在之前实验中不断的滚动鼠标时,实际上是在不断的调用debounced函数,不断的清除timeout对应的异步任务,然后又设置新的timeout异步任务。当我们停止的时间不超过2s时,timeout对应的异步任务还没有被触发,所以再次滚动鼠标触发debounced函数还可以清除timeout任务然后设置新的timeout任务。一旦停止的时间超过2s,最终的timeout对应的异步代码就会被执行。

    总结

    • 去抖是限制函数执行频率的一种方法。
    • 去抖后的函数在指定时间内最多被触发一次,连续触发去抖后的函数只能得到一次的触发效果。
    • underscore去抖的实现依赖于JavaScript的异步执行机制,优先执行同步代码,然后执行事件队列中的异步代码。

    参考

    更多underscore源码解读:GitHub

  • 相关阅读:
    联赛膜你测试20 T1 Simple 题解 && NOIP2017 小凯的疑惑 题解(赛瓦维斯特定理)
    P5518
    快速除法 / 取模
    P6860
    spoj LCMSUM
    虚树 学习笔记
    长链剖分 学习笔记
    CF526G
    P4292
    01 分数规划(water)
  • 原文地址:https://www.cnblogs.com/DM428/p/8511327.html
Copyright © 2011-2022 走看看