JavaScript高阶函数的用法和使用场景

Posted 三水草肃

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JavaScript高阶函数的用法和使用场景相关的知识,希望对你有一定的参考价值。

高阶函数

高阶函数需要满足的条件:

  1. 函数可以作为参数传递,
  2. 函数可以作为返回值输出。

函数作为参数传递

场景:回调函数。
作用:可以抽离数据(易变化)业务逻辑,把数据业务逻辑放在函数参数中,这样可以分离变化和不变的部分。

  1. 例子:当我们想在 ajax 请求之后接受数据,但是不知道何时请求完成,那么我们可以使用回调函数 callback 作为参数传入,请求完成之后执行 callback 函数
const getInfo = function (useId, callback) 
  Api("xx" + useId, function (data) 
    if (status === 200) 
      callback(data);
    
  );
;
getInfo(123, function (data) 
  console.log(data);
);
  1. 回调函数不仅在异步请求中,当一个函数不适合执行一些请求时,可以把这些请求封装成函数,把它作为函数传递给另外一个函数,委托另一个函数来执行
    1. 隐藏节点的逻辑放在回调函数中,我们可以控制隐藏节点,当节点创建好之后就会执行我们的回调函数
const appendDiv = function (divLen, callback) 
  for (let i = 0; i < divLen; i++) 
    const div = document.createElement("div");
    div.innerhtml = 1;
    document.body.appendChild(div);
    // div.style.display = "none"; // 比这种好了很多
    if (typeof callback === "function") 
      callback(div);
    
  
;
appendDiv(100, function (node) 
  node.style.display = "none";
);

函数作为返回值输出

函数作为返回值输出的应用场景更多,也更能体现函数式编程的巧妙。函数返回一个可执行的函数,意味着过程是可延续的。

1.判断数据的类型

const isType = function (type) 
  return function (obj) 
    return Object.prototype.toString.call(obj) === `[object $type]`;
  ;
;

console.log(isType("String")("123")); // true

高阶函数实现 AOP

AOP(面向切面编程)的主要作用是保证业务逻辑高内聚性。

  1. 把一些跟核心业务逻辑模块无关的功能抽离出来,这些跟业务逻辑无关的功能通常包括日志统计、安全控制、异常兜底。把这些功能抽离出来之后,再通过动态织入 1 的方式渗入业务逻辑中。
Function.prototype.before = function (beforeFn) 
  const _self = this;
  return function () 
    beforeFn.apply(this, arguments); // 执行 before 新函数
    return _self.apply(this, arguments); // 执行 func 函数
  ;
;

Function.prototype.after = function (afterFn) 
  const _self = this;
  return function () 
    let ret = _self.apply(this, arguments);
    afterFn.apply(this, arguments); // 执行 func 函数
    return ret; // 执行 after 函数
  ;
;

const func = function () 
  console.log(2);
;
func
  .before(function () 
    console.log(1);
  )
  .after(function () 
    console.log(3);
  )();

高阶函数的其他应用场景

  1. currying 函数柯里化
    调用函数时,明确带上了参数,会把参数保存起来,当我们不传参数时,才开始计算
// 场景:多次输入之后,计算结果值。比如每月花销,我只要月底计算得出结果就可以。这时候就可以用到函数柯里化
const cost = function () 
  const args = [];
  return function () 
    if (arguments.length === 0) 
      let res = args.reduce((p, n) => p + n);
      return res;
     else 
      [].push.apply(args, arguments);
    
  ;
;

const costs = cost();
costs(100);
costs(100);
console.log(costs());

// 使用高阶函数,抽离一下柯里化逻辑

const curry = function (fn) 
  const args = [];
  return function () 
    if (arguments.length === 0) 
      return fn.apply(this, args);
     else 
      [].push.apply(args, arguments);
      return arguments.callee;
    
  ;
;

const kcost = (function () 
  return function () 
    return [...arguments].reduce((p, n) => p + n);
  ;
)();
const cost1 = curry(kcost);
cost1(100);
cost1(100);
cost1(100);
console.log(cost1());
  1. uncurrying 让对象调用不属于自己的方法
    call、apply、bind 能解决这个问题
    const obj1 = 
      name: "obj1",
    ;
    const obj2 = 
      getName: function () 
        console.log(this.name);
      ,
    ;
    obj2.getName.call(obj1);
    
    1. 常借用 Array.prototype 的方法。Array.prototype 方法原本只可以操作 array 数组,但是通过 call、apply、bind 就可以把任意对象当作 this 传入
(function () 
  Array.prototype.push.call(arguments, 4);
  console.log(arguments); // [1, 2, 3, 4]
)(1, 2, 3);
  1. 实现 uncurrying,把范化的 this 提取出来
1. 实现第一种
Function.prototype.uncurrying = function () 
  const self = this;
  return function () 
    const obj = Object.prototype.shift.call(arguments); // 提取this
    return self.apply(obj, arguments);
  ;
;

const myPush = Array.prototype.push.myUncurrying();

const arrPush = [1, 2, 3];
(function () 
  myPush(arguments, 4);
  console.log([...arguments]); // [1, 2, 3, 4]
)(1, 2, 3);

const obj = 
  0: 1,
  length: 1,
;
myPush(obj, 2);
console.log(obj); //  '0': 1, '1': 2, length: 2 
1. 实现第二种uncurrying
Function.prototype.myUncurry2 = function() 
  const self = this;
  return function() 
    return Function.prototype.call.apply(self, arguments);
  

  1. 函数节流

    1. 函数被频繁调用。 函数本身的实现不合理,导致的函数频繁调用的性能问题。

    2. 函数节流解决的问题:函数被触发的频率太高,那么我们就要通过函数节流限制触发频率。节流可以用截流来理解,比如河流流速很快,影响河道通行安全,是不是要通过截流操作减缓河道流速。

    3. 场景

      1. window.resize()
        当浏览器窗口大小改变,就会触发 window.resize()。如果在 window.resize()里做一些 DOM 相关的操作,就会非常消耗性能的。界面可能会造成卡顿
      2. mousemove()
        当给 DOM 节点绑定了 mousemove()拖拽事件,如果在 mousemove()里做一些 DOM 相关的操作,就会非常消耗性能的。界面可能会造成卡顿
      3. 上传进度
        上传进度会频繁通知用户,一秒 10 次。很显然并不需要这么频繁提示用户
    4. 函数节流实现原理:执行的函数使用 setTimeout 延迟执行。如果延迟执行还没有完成,则不执行接下来函数。
      throttle 函数接受两个参数,第一个参数时需要被延迟执行的函数,第二个参数是延迟执行时间

    const throttle = function (fn, delay) 
      const self = fn; // 保存需要延迟执行的函数
      let timer,
        firstTime = true;
      return function () 
        const args = arguments;
        const that = this;
        if (firstTime) 
          self.apply(that, args);
          return (firstTime = false);
        
        if (timer) 
          return false;
        
        timer = setTimeout(function () 
          clearTimeout(timer);
          timer = null;
          self.apply(that, args);
        , delay);
      ;
    ;
    function fn() 
      console.log(1);
    
    window.onresize = throttle(fn, 500);
    
  2. 分时函数
    函数节流是用户主动调用导致的,也有一些客观原因导致严重影响性能的。比如一次性渲染上千上万的 DOM 节点,浏览器是吃不消的,会导致卡顿甚至假死
    解决方案是:

    1. 分批渲染节点,之前一秒渲染 1000 节点,改为 200ms 渲染 10 节点
    2. 用户可视范围内渲染节点,与虚拟列表的实现原理一致
    const TimeChunk = function (ary, fn, count, delay) 
      let obj, t;
      const len = ary.length;
    
      const start = function () 
        for (let i = 0; i < Math.min(count, ary.length); i++) 
          obj = ary.shift();
          fn(obj);
        
      ;
      return function () 
        t = setInterval(function () 
          if (ary.length === 0) 
            return clearInterval(t);
          
          start();
        , delay);
      ;
    ;
    
    const ary = [];
    for (let i = 1; i <= 1000; i++) 
      ary.push(i);
    
    const renderDomList = TimeChunk(
      ary,
      function (n) 
        const div = document.createElement("div");
        div.innerHTML = n;
        document.body.appendChild(div);
      ,
      10,
      200
    );
    renderDomList();
    

以上是关于JavaScript高阶函数的用法和使用场景的主要内容,如果未能解决你的问题,请参考以下文章

js数组高阶方法reduce经典用法代码分享

高阶组件具体用法和使用场景

高阶组件具体用法和使用场景

高阶组件具体用法和使用场景

高阶组件具体用法和使用场景

JavaScript高阶函数