underScore专题-compose函数,节流函数,防抖函数

compose函数

从要实现的效果理解复合函数:

    var fun = _.compose(fun1, fun2, fun3)
    fun(1)

把多个函数组合为一个函数,依次调用参数,并把组合后的参数传递的参数,依次传递给每一个函数,1先传递给fun3然后再把处理结果传递给fun2,再把处理结果传递给fun1最后返回fun1的处理结果。

如果不实用compose函数,就需要像下面这种方式去调用,可读性比较差。

    function fun1(a) {
        return a + 1
    }
    function fun2(a) {
        return a * 2
    }
    function fun3(a) {
        return a - 1
    }
    var res = fun3(fun2(fun1(1)))
    console.log(res)

underScore内部compose函数的实现就很简单:

    function compose() {
        var args = arguments;
        var start = args.length - 1;
        return function() {
            var i = start;
            var result = args[start].apply(this, arguments);
            while (i--) result = args[i].call(this, result);
            return result;
        };
    }

获取传递的函数: 

 var args = arguments;

倒序调用的开始位置,从最后一个函数开始调用: 

var start = args.length - 1;

因为不知道调用fun的时候会传递几个参数进来,所以第一次调用的时候,会使用arguments去获取。

 var result = args[start].apply(this, arguments);

倒序执行函数:

 while (i--) result = args[i].call(this, result);

react中使用compose函数来增强redux

store中的compose函数:

    function compose() {
        var args = arguments;
        var funs = []
        for (var i = 0; i < args.length; i++) {
            funs[i] = args[i]
        }
        if (funs.length === 0) {
            return function(args) {
                return args
            }
        }
        if (funs.length === 1) {
            return function(args) {
                return funs[0](args)
            }
        }
        return funs.reduce(function(a, b) {
            return function() {
                return a(b.apply(this, arguments))
            }
        })
    }

节流函数

先来看看使用例子,给window添加一个滚动时间,内部有执行代码打印日志:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<style>
    #root {
        height: 1000px;
        width: 100%;
    }
</style>

<body>
    <div id="root"></div>
</body>

<script src="underScore.js"></script>
<script>
    window.onscroll = function() {
        console.log('hello word')
    }
</script>

</html>

在页面滚动的过程中,这句话就一直在打印: 

换成节流函数:

    var throttle = _.throttle(function() {
        console.log('hello word')
    }, 3000)
    window.onscroll = throttle

滚动的时候不再一直打印,而是先立即执行一次,等待wait后再执行。每次操作触发很多次,但是函数都至少会调用两次。

可见,节流函数可降低函数执行的频率。

但是操作过程中会发现,只要第一次开始滚动,函数会立即执行一次,然后3s后再执行一次,说明这个节流函数默认调用了2次,第一次是立即执行,第二次是3s后执行。就像点击按钮去发送请求,通常会放一个节流函数,防止连续点击重复发送请求。一般会等待3s后再处理,而第一次请求都是希望点击立即去发送请求。

其实underScore中节流函数测参数有3个

    _.throttle(fun, wait, options)

fun:处理函数

wait:等待时间

options: {},leading: false 阻止立即执行,等待wait后再去执行。trailing: fasle, 先立即执行,如果距离上一次差wait,再触发再立即执行。

这两个属性之间的差异很微小,需要好好理解。

leading: 每次操作,注意是每次操作,每次操作都会触发很多次节流函数。每次操作都不会立即执行,而是等待wait时间后再执行第一次。

 var throttle = _.throttle(function() {
        console.log('hello word')
    }, 3000, {leading: false})
    window.onscroll = throttle

trailing:每次操作,立即执行一次,等待wait后再执行,函数都至少会执行一次。

   var throttle = _.throttle(function() {
        console.log('hello word')
    }, 3000, {trailing: false})
    window.onscroll = throttle

 先来实现节流函数第一版:不考虑参数

    function throttle(fun, wait, options) {
        // 给一个初始值
        if(!options) options = {}
        var preTime = 0, timeout, result,args;
        function later() {
            preTime = new Date().valueOf();
            clearTimeout(timeout)
            timeout = null
            result = fun.apply(this,args);
        }
        return function() {
            args = Array.prototype.slice.apply(this, arguments);
            var nowTime = new Date().valueOf();
            // remaining<=0说明,时间差大于等于wait,可以触发函数了
            // remaining > 0,说明时间差小于wait 还不能触发,需要等待wait后再触发
            var remaining = wait - (nowTime - preTime)
            // 立即执行
            if(remaining <= 0) {
                if(timeout) {
                    clearTimeout(timeout)
                    // 释放内存
                    timeout = null;
                }
                preTime = nowTime;
                result = fun.apply(this,args);
                // 防止重复创建很多个timeout 延时执行
            } else if(!timeout){
                timeout = setTimeout(later, wait)
            }
            return result
        }
    }

起始preTime是0 remaining一定是小于0的,立即执行第一次,然后改变preTime的值,返回的函数再次调用,如果时间间隔小于wait,会走到else if再没有timeout的情况下,创建一个异步,等待执行,再次调用回调函数,如果时间间隔还是小于wait就什么也不做,wait时间到了以后,异步会执行。preTime也改变了,如果不改变这个值,这个时候remaining应该是小于0了,会再立即执行一次。 这是一个循环结束,这里timeout被清空,所以又会开始下一个循环。

第二版,options:

tranining:false,是立即执行,但是不会每次都执行两次,满足条件才会执行两次。

由上面的代码可以知道,之所以会每次操作都至少触发两次,是因为:

else if(!timeout){
       timeout = setTimeout(later, wait)
}

这句话在每次立即执行后,preTime发生改变,时间差小于0, timeout为null的时候都会创建一个异步任务。所以只需要在这里加一个判断就可以阻止这次执行。

else if(!timeout && options.traning !== false){
  timeout = setTimeout(later, wait)
 }

leading:false是阻止立即执行,改变时间差就可以阻止立即执行:

if (!previous && options.leading === false) previous = _now;

later中也需要做下调整:

previous = options.leading === false ? 0 : now();

所以完整的代码:

    function throttle(fun, wait, options) {
        // 给一个初始值
        if(!options) options = {}
        var preTime = 0, timeout, result,args;
        function later() {
            preTime = options.leading === false ? preTime = 0 : new Date().valueOf();
            clearTimeout(timeout)
            timeout = null
            result = fun.apply(this,args);
        }
        return function() {
            args = Array.prototype.slice.apply(this, arguments);
            var nowTime = new Date().valueOf();
            // 阻止立即执行
            if(!preTime && options.leading === false) preTime = now
            // remaining<=0说明,时间差大于等于wait,可以触发函数了
            // remaining > 0,说明时间差小于wait 还不能触发,需要等待wait后再触发
            var remaining = wait - (nowTime - preTime)
            if(remaining <= 0) {
                if(timeout) {
                    clearTimeout(timeout)
                    // 释放内存
                    timeout = null;
                }
                preTime = nowTime;
                result = fun.apply(this,args);
                // 释放内存
                if (!timeout) args = null;

                // 防止重复创建很多个timeout
            } else if(!timeout && options.trailing !== false){
                timeout = setTimeout(later, wait)
            }
            return result
        }
    }

 

防抖函数debounce

将若干函数调用合成为一次,并在给定时间过去之后,或者连续事件完全触发完成之后,调用一次(仅仅只会调用一次),引入前辈的解释就是,你尽管触发事件,但是我一定在事件触发 n 秒后才执行,如果你在一个事件触发的 n 秒内又触发了这个事件,那我就以新的事件的时间为准,n 秒后才执行,总之,就是要等你触发完事件 n 秒内不再触发事件,我才执行。

一个简单的防抖函数:

function debounce(fun, delay) {
    var timeout, content = this, args;
    function later() {
        fun.apply(content, args)
    }
    return function() {
        if (timeout) clearTimeout(timeout)
        timeout = setTimeout(later, delay)
    }
}

操作过程中创建的所有的timeout都被清除,只有最后一次执行的么有被🆑,所以会在delay后调用一次。

立即执行:

有时候我们又希望函数可控制的立即执行一次,然后再在触发停止后ns内执行一次。

function debounce(fun, delay, immediate) {
    var timeout, content = this, args, result;
    function later() {
        // 这里清空,immediate为true下次再触发的可以触发立即执行
        timeout = null
        result = fun.apply(content, args)
    }
    return function() {
        args = arguments;
        // 这里只是清除了定时器,但是timeout的值存在,并且在变化
        if (timeout) clearTimeout(timeout)
        console.log(timeout) // undefined 1 2 3 ...
        if(immediate) {
            var callNow = !timeout;
            if(callNow ) result = fun.apply(content, args)
            timeout = setTimeout(later, delay)
        } else {
            timeout = setTimeout(later, delay)
        }
        
       return result; 
    }
}

返回值:

如果fun 函数有返回值,就需要把返回值返回,不能让其丢失。所以用一个参数接受返回值,最后再返回。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值