典型例子:节流与防抖
简单的防抖节流
节流与防抖大家应该都再熟悉不过了,下面我分别列出简单的防抖与节流。
// 防抖
function debounce(func,wait){
let timer=null;
return function(){
console.log(timer)
if(timer){
clearTimeout(timer);
}
timer=setTimeout(() => {
func.apply(this,arguments);
}, wait);
}
}
// 节流
function myThrottle(func, wait) {
let flag=true;
return function(...args){
if(!flag) return;
flag=false;
func.apply(this,args);
setTimeout(() => {
flag=true;
}, wait);
}
}
但是其实还有很多情况我们没有考虑到,如果在某些特定的需求下使用上述代码是不行的,比如说一个输入事件,我们使用简单的防抖,wait设置为3s,此时对应的规则为用户输入→用户停止,等待3s,执行func
。
可控的防抖节流
但是现在我们有一个奇怪的需求,我们需要用户在输入的时候能够立马执行func,之后再进行用防抖,也就是说现在我们的规则变成了:用户输入(立马执行func)→用户停止,等待3s,执行func,n秒过后,用户输入(立马执行func)→用户停止,等待3s,执行func....
,此时我们需要再次封装我们的函数,使得这种奇怪的需求变得可控。
在下列代码中,我们额外添加了一个可选的option对象,在该对象里控制开始与结尾是否执行func
// 可控制的防抖
//leading: 开始时是否执行,trailing: 结束时候是否执行
function debounce(func,wait,option ={leading:false,trailing:true}){
let timer=null;
return function(){
let isInvoked=false;
//没有定时器(本次轮询开始)且leading为true
if(timer == null && option.leading){
func.apply(this,arguments);
isInvoked=true;
}
clearTimeout(timer);
timer=setTimeout(() => {
//本次输入事件开始时未执行过func且trailing为true
if(option.trailing && !isInvoked){
func.apply(this,arguments);
}
timer=null;
}, wait);
}
}
//可控制的节流
function throttle(func,wait,option = {leading: true,trailing: true}){
let flag=true;
let preArgs=null;
function setTime(){
//trailing为true,一直执行定时器
if(preArgs && option.trailing){
func.apply(this,preArgs);
preArgs=null;
setTimeout(setTime, wait);
}else{
flag=true;
}
}
return function(){
if(!flag){
preArgs=[...arguments];
}else{
if(option.leading){
func.apply(this,arguments);
}
flag=false;
setTimeout(setTime,wait)
}
}
}
上述代码我们额外添加了一个可选的option对象,来进一步控制防抖与节流的行为,现在我们只需要通过设置{leading:true,trailing:true}来实现:
用户输入(立马执行func)→用户停止,等待3s,执行func,等待n秒,用户输入(立马执行func)→用户停止,等待3s,执行func
。
柯里化
简单柯里化
const join = (a, b, c) => {
return `${a}_${b}_${c}`
}
//柯里化join函数
const curriedJoin = curry(join)
curriedJoin(1, 2, 3) // '1_2_3'
curriedJoin(1)(2, 3) // '1_2_3'
curriedJoin(1, 2)(3) // '1_2_3'
柯里化也是js中比较常用的概念,现在我们来尝试写出一个 curry 函数,实现上面的效果,接收一个函数然后将该函数柯里化后并返回。
function curry(func) {
return function innerFunc(...args) {
//func为原函数,innerFunc为新函数
//如果新函数的参数比原函数多或相等则执行原函数
if(args.length >= func.length){
return func(...args)
}else{
//否则一直累加新函数的参数
return function(...next){
return innerFunc(...args,...next)
}
}
}
}
或者
function curry(func) {
return function innerFunc(...args) {
if(args.length >= func.length){
return func(...args)
//return func.apply(this,args)
}else{
//这里的bind作用是参数继承而不是改变this(我们应该清楚function内this都为window)
return innerFunc.bind(this,...args)
}
}
}
点此查看更完整的柯里化解析
支持占位符
现在我们已经实现了一个简单的curry函数,但是现在我想让我们的curry函数能够支持占位符,应该怎么做呢?
const join = (a, b, c) => {
return `${a}_${b}_${c}`
}
const curriedJoin = curry(join)
const _ = curry.placeholder
curriedJoin(1, 2, 3) // '1_2_3'
curriedJoin(_, 2)(1, 3) // '1_2_3'
curriedJoin(_, _, _)(1)(_, 3)(2) // '1_2_3'
function curry(fn) {
return function innerFunc(...args){
//如果参数都不是placeholder且长度满足,执行fn
const complete=args.length >= fn.length && !args.slice(0,fn.length).includes(curry.placeholder);
if(complete){
return fn.apply(this,args);
}else{
return function(...next){
//依次替换上一个函数的placeholder
const res=args.map(arg => arg === curry.placeholder && next.length ? next.shift() : arg);
return innerFunc(...res, ...next);
}
}
}
}
curry.placeholder = Symbol()
补充
事实上,在js中这些封装函数,高阶函数,我们使用的非常多,如扁平化,链式调用,pipe,EventEmitter,memo等等,在这里也给大家推荐一个关于javascript,typescript的题库,可以多刷一刷。
上述两个例子摘自本人github,大家感兴趣的可以自行观看。