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 函数有返回值,就需要把返回值返回,不能让其丢失。所以用一个参数接受返回值,最后再返回。