高阶函数的使用

Toby

在这里记录一些比较实用的高阶函数

只执行一次

这是一个只会执行一次的函数,该函数只会执行一次,这对于某些场景,如事件监听、回调函数,以及其他只需执行一次的函数非常有用。

// fun.js
export function once(fn, replacer = null) {
  return function (...args) {
    if (fn) {
      const ret = fn.apply(this, args);
      fn = null;
      return ret;
    }
    if (replacer) {
      // return replacer.apply(this, args);
      return replacer.apply(this, ['这个方法只被调用一次']);
    }
  };
}

参数

  • fn:接收一个函数,表示要执行的目标函数。
  • replacer:可选参数,是一个函数,用于替代原始目标函数的执行结果。

返回值

  • 返回一个新的函数,该函数能够保证 fn 只执行一次,如果有多次调用,只有第一次调用会执行 fn。

函数功能

  • once 函数返回一个新的函数,这个新函数有自己的作用域,可以访问 once 函数的参数和内部变量。
  • 当新函数第一次被调用时,它会执行原始目标函数 fn 并传入相应的参数。
  • 在第一次调用后,新函数会将原始目标函数 fn 设置为 null,这样即使再次调用新函数,原始目标函数 fn 也不会被执行。
  • 如果在第一次调用后再次调用新函数,并且提供了 replacer 参数,那么新函数将会执行 replacer 函数,并返回其结果。否则,后续调用将会返回 undefined。

调用实例

<div id='do-once'>只执行一次</div>

const botton = document.getElementById('do-once')
button.addEventListener('click', once((evt) => {
  ...
  console.log('点击按钮:',1,2);
},
  (e) => {
    console.log('e: ', e);
  }
));
<div @click="doOnce(1,2)">只执行一次</div>

import { once } from '@/utils/fun'

export default {
  methods: {
    doOnce: once((a, b) => {
      ...
      console.log('点击按钮: ', a, b)
    }, (e) => {
      console.log('e: ', e)
      // throw new Error(e);
    }),
  }
}
<div @click="doOnce(1,2)">只执行一次</div>

import { once } from '@/utils/fun'

const doOnce = once((e,b)=> {
  ...
  console.log('点击按钮:',e,b);
},(e)=>{
  console.log('e: ', e);
  // throw new Error(e);
})

输出结果:
当点击第一次的时候会输出 => 点击按钮: 1 2
之后的点击都会输出 => e: 这个方法只被调用一次

节流

这是一个节流函数,用于限制函数的执行频率,这在处理高频率事件(例如滚动、拖拽等)时非常有用,以避免过多地触发函数执行。

// fun.js
export function throttle(fn, ms = 1000) {
  let throttleTimer = null;
  return function (...args) {
    if (!throttleTimer) {
      const ret = fn.apply(this, args);
      throttleTimer = setTimeout(() => {
        throttleTimer = null;
      }, ms);
      return ret;
    }
  };
}

参数

  • fn:接收一个函数,表示要执行的目标函数。
  • ms:可选参数,表示时间间隔(毫秒),用于限制目标函数的执行频率。如果不提供 ms,默认为 1000 毫秒(1 秒)。

返回值

  • 返回一个新的函数,该函数能够保证目标函数 fn 在指定的时间间隔内只能执行一次。

函数功能

  • throttle 函数返回一个新的函数,这个新函数有自己的作用域,可以访问 throttle 函数的参数和内部变量。
  • 当新函数第一次被调用时,它会执行原始目标函数 fn 并传入相应的参数。
  • 在第一次调用后,新函数会设置一个定时器 throttleTimer,在指定的时间间隔 ms 后,定时器会被触发。
  • 一旦定时器触发后,新函数会再次允许执行原始目标函数 fn,并重置定时器,保持执行间隔。

调用实例

<div id='door'>节流</div>

const botton = document.getElementById('door')
button.addEventListener('mousemove', throttle((e) => {
  ...
  console.log('鼠标移动到哪个位置:',1,2);
}));
<div @mousemove="door(1,2)">节流</div>

import { throttle } from '@/utils/fun'

export default {
  methods: {
    door: throttle((a, b) => {
      ...
      console.log('鼠标移动到哪个位置: ', a, b)
    }),
  }
}
<div @mousemove="door(1,2)">节流</div>

import { throttle } from '@/utils/fun'

const door = throttle((e,b)=> {
  ...
  console.log('鼠标移动到哪个位置:',e,b);
})

输出结果:
函数每 ms 毫秒内只执行一次 => 鼠标移动到哪个位置: 1 2

防抖

这是一个防抖函数,用于处理高频率事件的情况,只有在事件停止触发一段时间后,才会执行目标函数,这有助于避免频繁执行函数并提高性能。
在实际开发中,它通常用于处理用户输入、浏览器窗口调整、搜索框实时查询等场景。

// fun.js
export function debounce(fn, ms = 1000) {
  let debounceTimer = null;
  return function (...args) {
    if (debounceTimer) clearTimeout(debounceTimer);

    debounceTimer = setTimeout(() => {
      fn.apply(this, args);
    }, ms);
  };
}

参数

  • fn:接收一个函数,表示要执行的目标函数
  • ms:可选参数,表示时间间隔(毫秒),用于指定在调用函数之后多久才执行目标函数。如果不提供 ms,默认为 1000 毫秒(1 秒)。

返回值

  • 返回一个新的函数,该函数通过定时器机制来确保目标函数 fn 在最后一次调用之后的指定时间间隔内才会被执行。

函数功能

  • debounce 函数返回一个新的函数,这个新函数有自己的作用域,可以访问 debounce 函数的参数和内部变量。
  • 每次新函数被调用时,它会清除之前设置的定时器 debounceTimer,这样可以确保只有最后一次调用才会触发目标函数的执行。
  • 新函数会设置一个新的定时器 debounceTimer,在指定的时间间隔 ms 后,定时器会被触发,从而执行目标函数 fn。

调用实例

<div id='door'>防抖</div>

const botton = document.getElementById('door')
button.addEventListener('click', debounce((e) => {
  ...
  console.log('按钮点击触发:',1,2);
}));
<div @click="door(1,2)">防抖</div>

import { debounce } from '@/utils/fun'

export default {
  methods: {
    door: debounce((a, b) => {
      ...
      console.log('按钮点击触发: ', a, b)
    }),
  }
}
<div @click="door(1,2)">防抖</div>

import { debounce } from '@/utils/fun'

const door = debounce((e,b)=> {
  ...
  console.log('按钮点击触发:',e,b);
})

输出结果:
当用户频繁点击按钮的时候,无论点击多少次都不会触发函数
只有等用户最后一次点击完毕的 ms毫秒后才会触发 => 按钮点击触发: 1 2

拦截器

这是一个拦截器,函数提供了在调用目标函数前后执行额外操作的能力,可以在 beforeCall 函数中执行前置操作,并根据返回值来决定是否继续执行目标函数,然后在 afterCall 函数中执行后置操作,并可以根据需要处理目标函数的返回值。这样可以灵活地拓展目标函数的功能,例如添加日志、数据验证等功能。

// fun.js
export function intercept(fn, { beforeCall = null, afterCall = null }) {
  return function (...args) {
    if (!beforeCall || beforeCall.call(this, args) !== false) {
      // 如果beforeCall返回false,不执行后续函数
      const ret = fn.apply(this, args);
      if (afterCall) return afterCall.call(this, ret);
      return ret;
    }
  };
}

参数

  • fn:接收一个函数,表示要执行的目标函数。
  • {beforeCall, afterCall}:一个配置对象,用于定义在执行目标函数前后所执行的函数。beforeCall 和 afterCall 都是可选参数,分别表示在调用目标函数前和调用目标函数后执行的函数。

返回值

  • 返回一个新的函数,该函数能够在执行目标函数前后执行指定的操作,并可以根据情况控制是否执行目标函数。

函数功能

  • intercept 函数返回一个新的函数,这个新函数有自己的作用域,可以访问 intercept 函数的参数和内部变量。
  • 在新函数被调用时,它首先检查是否提供了 beforeCall 函数,并调用它。如果 beforeCall 函数返回 false,则不会执行后续的目标函数,并直接返回。
  • 如果没有提供 beforeCall 或者 beforeCall 返回值不是 false,新函数会执行目标函数 fn 并传入相应的参数。
  • 如果提供了 afterCall 函数,新函数会在执行完目标函数后调用它,并将目标函数的返回值传递给 afterCall 函数。然后,新函数将返回 afterCall 的返回值。

调用实例

<div id='myIntercept'>拦截器</div>

const botton = document.getElementById('myIntercept')
button.addEventListener('click', intercept((evt) => {
  ...
  console.log('在这执行函数内容:',1,2);
},
  {
    beforeCall (args) {
      console.log('执行函数之前:', ...args)
    // 在此处执行你的逻辑,如果需要阻止myIntercept的调用,可以返回false
    // return false;
    },
    afterCall (result) {
      console.log('函数被执行后:', result)
    // 在此处执行你的逻辑
    }
  }
));
<div @click="myIntercept(1,2)">拦截器</div>

import { intercept } from '@/utils/fun'

export default {
  methods: {
    myIntercept: intercept((a, b) => {
      // 这里可以放一些在调用之前的逻辑
      console.log('在这执行函数内容: ', a, b)
    },
      {
        beforeCall (args) {
          console.log('执行函数之前:', ...args)
        // 在此处执行你的逻辑,如果需要阻止myIntercept的调用,可以返回false
        // return false;
        },
        afterCall (result) {
          console.log('函数被执行后:', result)
        // 在此处执行你的逻辑
        }
      }
    ),
  }
}
<div @click="myIntercept(1,2)">拦截器</div>

import { intercept } from '@/utils/fun'

const myIntercept = intercept((e,b)=>{
  console.log('在这执行函数内容: ', e,b);

},
  {
    beforeCall (args) {
      console.log('执行函数之前:', ...args)
    // 在此处执行你的逻辑,如果需要阻止myIntercept的调用,可以返回false
    // return false;
    },
    afterCall (result) {
      console.log('函数被执行后:', result)
    // 在此处执行你的逻辑
    }
  }
)

intercept的第二个参数是一个对象,可以提供 beforeCall、afterCall 两个拦截器函数,
分别“拦截”fn 函数的执行前和执行后两个阶段。
输出结果为:
执行函数之前: 1 2
在这执行函数内容: 1 2
函数被执行后: result

总结

实际上,在函数式编程中,高阶函数的概念非常广泛,几乎在任何需要对函数进行操作的情况下,都可以使用高阶函数的概念。它们可以用来简化代码、提高可读性,并让代码更加模块化和易于维护。

更新时间 2023/7/24 11:23:05