高阶函数 | 青训营笔记

140 阅读4分钟

这是我参与「第四届青训营 」笔记创作活动的的第4天

参考了月影老师课堂上使用的代码。

什么是高阶函数

高阶函数中的参数为函数,返回值也是函数。在高阶函数中返回函数可以使用父函数里参数和变量,形成了闭包,高阶函数中的变量会留在内存中不被清理。

闭包

  • 闭包是:嵌套在函数中的内部函数使用了函数中的变量。
  • 如图当外部函数A被调用,返回内部函数B使用外部函数A的变量c时,A函数中的变量c会存在B中,不会被清理。 Whiteboard (2).png

常用高阶函数

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 }
  • 非纯函数越多,系统可维护性越差。
  • 使用高阶函数大大减少了非纯函数的使用,可以提高系统的可维护性。

节流防抖进一步理解资料