这是我参与「第四届青训营 」笔记创作活动的的第4天
参考了月影老师课堂上使用的代码。
什么是高阶函数
高阶函数中的参数为函数,返回值也是函数。在高阶函数中返回函数可以使用父函数里参数和变量,形成了闭包,高阶函数中的变量会留在内存中不被清理。
闭包
- 闭包是:嵌套在函数中的内部函数使用了函数中的变量。
- 如图当外部函数A被调用,返回内部函数B使用外部函数A的变量c时,A函数中的变量c会存在B中,不会被清理。
常用高阶函数
Once单次调用函数
- 在第一次调用后,
Once的参数fn在返回的函数中变成了null,已经形成闭包,故第二次调用时不会执行return语句。 Once表示只被请求一次,可以应用在异步请求的场景中,比如防止用户多次请求时阻塞或元素添加、移除时报错,有的业务都会用到只需要执行一次的逻辑,故把该逻辑抽象出来形成Once函数。function once(fn) { return function(...args) { if(fn) { const ret = fn.apply(this, args); fn = null; return ret; }}}
Throttle节流函数
- 用来限制用户触发事件频率,比如点击按钮时设定点击间隔为0.5s;若用户疯狂点击按钮,则每隔0.5秒触发事件。
- 常用于鼠标滚动、鼠标移入移出、点击等事件,可以用在拖拽、下拉加载等场景。
- 当用户连续执行某事件时,timer != null,此时传入的函数不会执行,要等到time时间间隔后,time = null方可执行。
function throttle(fn, time = 500){ let timer; return function(...args){ if(timer == null){ fn.apply(this, args); timer = setTimeout(() => { timer = null; }, time)}}}
Debounced防抖函数
当用户触发所有动作完成时再执行操作,避免用户多次触发多次执行操作。
常用场景:
- 自动保存功能。比如掘金笔记插件:当用户完成输入时再保存,而非用户敲键盘逐字保存,防止大量请求服务器给服务器带来负担。
- 淘宝、百度等输入查询功能。可以用于input上的change事件,当用户完成输入时;即用户停止敲击键盘时查询。
- 可用在窗口缩放事件上(window.resize),当用户停止缩放窗口时再计算DOM尺寸。
- 月影老师课堂上Flappy Bird例子:当用户鼠标停止移动时,小飞鸟移动到鼠标处。
- 当用户多次重复触发事件时(在比time间隔还短的时间内),都会清除定时器制止fn函数执行;直到触发至最后一次时(停止时间大于time),才会执行最后触发事件的函数fn。
function debounce(fn, dur){ //防抖函数 dur = dur || 100; var timer; return function(){ clearTimeout(timer); //用户每次触发都清除计时器,制止计时器timer timer = setTimeout(() => { fn.apply(this, arguments); //函数在dur时间后执行 }, dur);}}
Consumer延时函数
- task数组负责存储触发的事件。
- 在返回的函数中,
task.push(fn.bind(this,...args))表示插入调用的事件,此时fn函数绑定到调用者的this上,包括其携带的参数。若在全局环境下调用函数,此时调用者是window。 - 任务列表没执行完时,timer!=null。
task.shift().call(this)每隔time间隔执行一次,直到执行完task中任务为止,可表示为task.shift().call(fn())。- 等执行完task中所有任务则timer变成null。
function consumer(fn,time){ let task = [], timer return function(...args){ task.push(fn.bind(this,...args)) if(timer == null){ timer = setInterval(()=>{ task.shift().call(this) if(tasks.length<=0){ clearInterval(timer) timer = null } },time)}}}
Iterative迭代函数
- 如果传入的参数subject是可以迭代的对象,则对subject中的所有对象行函数fn,把每个执行结果插入结果数组中最后全部返回。
- 优点:当要对大量可迭代的对象进行处理时,可以使用iterative迭代函数,从而减少大量for循环的使用,减少代码量。
- 其他类似对数组处理的迭代函数:forEach、some、every等。
function iterative(fn) { return function(subject, ...rest) { if(isIterable(subject)) { //如果subject可迭代 const ret = []; for(let obj of subject) { ret.push(fn.apply(this, [obj, ...rest])); } return ret; //返回要要被执行的数组 } return fn.apply(this, [subject, ...rest]); }} //不可迭代只调用一次
为什么要用高阶函数?
- 首先引出纯函数和非纯函数的概念。
- 纯函数:不会改变函数外部的值,结果可预期,不会产生副作用。比如
function (a){ return a } - 非纯函数:可能会改变函数外部的值,造成的结果是有副作用的。比如
function (){ return ++a } - 非纯函数越多,系统可维护性越差。
- 使用高阶函数大大减少了非纯函数的使用,可以提高系统的可维护性。