常用高阶函数(HOF) - 单次执行、节流、防抖 | 青训营笔记
🏝 前言
这是我参与「第四届青训营」笔记创作活动的的第4天😺
写好JavaScript,需要掌握过程抽象的思想。对于过程抽象,其既是用来处理局部细节控制的一些方法,也是函数式编程思想的基础应用。而 高阶函数(HOF) 便是过程抽象思想的体现之一。
在这篇笔记中,将记录三种常用的高阶函数,以及使用高阶函数的好处。
💡 高阶函数
概念
高阶函数 HOF(Higher-Order Function)
- 以函数作为参数
- 以函数作为返回值
- 常用于作为函数装饰器
function HOF(fn){
return function(...args){
return fn.appply(this,args);
}
}
常用高阶函数
1.单次执行 (once)
在页面中有一按钮,要求为其添加事件监听器:当这个按钮被点击时,
- 在控制台打印 "hello world!" ;
- 并要求这个动作只执行一次,若之后按钮再次被点击,打印动作也不会执行。
我们通常使用的方法是 addEventListener,并且在可选参数options中将 once 设置为 true ,意为只执行一次。
const btn = document.getElementById('btn');
document.addEventListener('click',()=>{
console.log('hello world!');
},{once:true});
但对于一些不能配置可选参数、但也有“只执行一次”需求的事件,我们要如何实现其需求?
🍊为了能够让“只执行一次“的需求覆盖不同的事件处理,我们可以将这个需求剥离出来。这个过程我们称为过程抽象。
结合高阶函数的定义,我们可以针对“只执行一次”的需求定义一个 once() 高阶函数,并将要处理的事件作为函数传入其中:
function once(fn){
return function(...args){
if(fn){
const ret = fn.apply(this,args);
fn = null;
return ret;
}
}
}
首先向传入一个作为参数的回调函数,得到一个其返回的 新函数 ;
调用 新函数 时,会对函数指针进行检测:
-
若指针非空,则执行该回调函数;
-
否则无操作。
将回调函数执行一遍后,把指针置空;
当再次调用 新函数 时,由于函数指针指向空,所以无操作,达到只执行一次的目的。
🍏例子
以一个计算两参数之和的函数为例子,要求该函数只能执行一次:
const once_demo = once(function(a,b){
return a+b;
});
console.log(once_demo(1,1));
console.log(once_demo(2,2));
可以观察到控制台的打印结果:
令该函数执行两次,实际上只有第一次有效果。
对于第一次传入的两个相同参数 1 ,打印结果为 2 ;
对于第二次传入的两个相同参数 2 ,打印结果为 undefined ,因为这次并没有执行求和函数。
2.节流 (throttle)
还是以页面中的按钮为例子,我希望用户在点击这个按钮时,
- 控制台中会打印 "hello world!" ;
- 为按钮发挥作用添加冷却时间,如500ms。
这意味着 addEventListener 绑定的函数在执行一次后会进入500ms的冷却时间,当这一冷却时间结束后,点击按钮才能产生打印"hello world!"的效果;但下一次打印操作完成后,按钮又会再次进入冷却。
在冷却过程中,无论用户点击多少次按钮,都不会有打印的操作。
这样的限制条件称为节流(throttle)。下面是一个针对节流的高阶函数:
function throttle(fn,time = 500){
let timer;
return function(...args){
if(timer == null){
fn.apply(this,args);
timer = setTimeout(()=>{
timer = null;
},time);
}
}
}
对于使用高阶函数throttle()生成新函数时,传入的参数除了包含被调用的回调函数外,还需要传入一个间隔的时间time,回调函数的执行便以time为时间间隔。具体时间可由使用者指定。
需要注意的是 setTimeout 函数的返回值:
对于代码段:
timer = setTimeout(()=>{
timer = null;
},time);
timer的值为 setTimeout 函数的返回值时,timer非空,回调函数并不会执行;
但在 setTimeout 函数中,会以500ms(之前设置了time = 500)为间隔执行语句timer = null ,使得timer为空,回调函数得以执行。
3.防抖 (debounce)
页面上存在一个内容被监听的多行输入框,对于这一输入框:
- 当输入框中的内容发生变化时,控制台实时打印输入框中的内容;
- 但必须在用户无输入操作500ms后,才允许进行打印操作。
如果不添加 “用户无输入操作若干秒后才打印” 的限制条件,当输入框的内容稍有变化时,控制台便会马上打印;当内容变化的次数一多,控制台打印操作便会变得频繁,产生抖动。
对于一些不希望产生抖动的操作,则需要添加限制条件,称为 防抖(debounce) 。下面是一个针对防抖的高阶函数:
function debounce(fn,dur = 500){
let timer;
return function(...args){
clearTimeout(timer);
timer = setTimeout(()=>{
fn.apply(this,args);
},dur);
}
}
参数dur为等待无操作所持续(duration)的时间。
每次在执行debounce()返回的新函数前,都要使用clearTimeout()方法取消setTimeout()的定时,即打断当前的等待间隔时间和执行规律,随后再重新设置一个定时器。
在新设置的定时器中,以dur为时间间隔执行回调函数。
🔎 为什么要使用高阶函数?
纯函数与非纯函数
纯函数 (pure function)
- 对外部没有副作用
- 效果可预期
非纯函数 (inpure function)
- 对外部有副作用
- 测试时要构造特定的环境、初始化和还原
- 测试难度更大
⚠️非纯函数越多,可维护性就越差,所以尽量使用纯函数。
使用高阶函数的好处
- 过程抽象
- 高阶函数都是纯函数,效果可预期
- 提升代码的可维护性
📝小结
这篇笔记记录了常用高阶函数中的单次执行(once)、节流(throttle)、防抖(debounce),以及高阶函数的定义、使用高阶函数的好处。除此之外,还对 纯函数(pure function) 与 非纯函数(inpure function) 做了简单的介绍。
常用高阶函数除了文中介绍的三种外,还包含了如同步触发异步执行函数consumer()、迭代函数iterative()等。而对于如何写好JavaScript,还要下不少的功夫,其中便包含了对过程抽象思想的熟练掌握。💨
2022/7/27