十分钟搞定JS高阶函数

92 阅读4分钟

高阶函数

简述

  • 概念:函数的参数是函数 || 函数返回了函数

  • 典型应用场景:为原有函数新增逻辑,而不破坏该函数的执行逻辑[AOP]

    // 业务逻辑
    function say(a,b){
        console.log('say',a,b)
    }

    // 高阶函数,参数为函数,为函数添加方法并执行该方法。
    Function.prototype.before = function(callback){
        return (...args)=>{
            // 1.切片函数,为函数提供前置逻辑;2....args传递原函数的参数
            callback();
            // 箭头函数保留原函数中的this => say
            this(...args);
        }
    }
    // 切片逻辑
    let beforeSay = say.before(function(){
        console.log('before say');
    })
    beforeSay('hello','world');

函数柯里化、反柯里化

核心思想

  • 一种预先存储的思想,通过闭包实现。自定义条件,将逻辑细分,避免重复逻辑进行。
  • 返回一个函数,函数保存了环境变量(前置逻辑),类似bind的逻辑!!!。
    • 好处:变量复用,且不用重复执行重复逻辑
  • 应用场景
    • 根据浏览器类型返回不同的绑定事件的方法。
    • 细分函数判断类型的方法。

场景实现:判断变量的类型。作用:细分方法,细化逻辑

  • 相关知识点
    • typeof -- 不能判断对象的类型
    • constructor -- 通过该属性可以看到原变量是通过什么方式构造出来的,可以修改
    • instanceof -- 判断该方法可以判断原变量是否为目标类型的实例 __proto__
    • Object.prototype.toString.call()

例子:以参数数量为条件的柯里化

/*
    通用型柯理化函数 
      当传入的参数满足形参数量时,函数执行。
      否则返回媒介函数,继续接收参数[缓存参数信息]。
*/
  const curring = (fn,arr= [])=>{
      // ... 此处可以写若干逻辑判断
      // 判断形参数量,只有当参数数量满足形参数量时,才执行函数
      let len = fn.length; 
      return function(...args){
          // arr为参数个数
          let params =  [...arr,...args]; 
            // 自定义函数执行条件
          if(params.length < len ){
              // 持续返回一个函数,继续接收参数
              return curring(fn,params) 
          }else{
              // 接收到最后一个参数时,执行函数
              return fn(...params) 
          }
      }
  }

  // 例子:类型判断
  const isTypeCurrying = (type,value) => {
      console.log(value)
      console.log(`type`,`[object ${type}]`)
      return Object.prototype.toString.call(value) === `[object ${type}]`
  }
  const isArray = curring(isTypeCurrying)('Array');
  const utils = {};
  const typeArray = ['Number','String','Boolean','Null','Undefined'];
  // 批量获取isType方法
  typeArray.forEach(type=>{
      utils['is' + type] = curring(isTypeCurrying)(type)
  })

柯里化:事件绑定API兼容

// 例子:事件绑定方式判断
const whichEvents = (()=>{
    if(window.addEventListen){
        return function(ele,type,listener,useCapture){
            ele.addEventListen(type,function(...args){
                listener.call(ele,...args)
            },useCapture)
        }
    }else{
        return function(ele,type,handle){
            ele.attachEvent('on'+type,function(...args){
                handle.call(ele,...args)
            })
        }
    }
})

同步函数迭代 -- compose函数

  • 函数嵌套执行,并把上一个函数的执行返回结果传给下一个函数
const add = (x, y) => x + y;
const square = (x) => x * x;

// 简单包裹
const value = square(add(1, 2));

// 多函数复杂包裹 - 无异步
const compose = (...funcs) => {
  let len = funcs.length;
  if (len === 0) return (x) => x;
  if (len === 1) return funcs[0](x);
  // 普通写法
  return (...args) => {
    let last = funcs.pop();
    let ret = last(...args);
    funcs.forEach((fn) => {
      ret = fn(ret);
    });
    return ret;
  };

  /*
    redux源码 
      => 逆推(cur放里面) 
      => 第一轮 square(square(...args)) 
      => 第二轮 square(square(square(...args)))
      => 第三轮 square(square(square(add(...args))))

  */
  //  大神写法
  // return funcs.reduce((pre, cur) => {
  //   return (...args) => {
  //     return pre(cur(...args));
  //   };
  // });
};
const fn = compose(square, square, square, add); // 先add再square
console.log(fn(1, 2));

同步函数迭代:经典数字累加

  /*
    例子:数字累加 -- add(1,2)(3)(4); add(1,2,3) add()
    需求分析
      函数能一直执行,所以返回的是函数,且逻辑相同,可能是函数本身。
      后续返回的函数能保持之前的运算结果 -> 利用闭包锁定了作用域链,共用变量
      重写函数转换数字的方法
        将函数转为字符串经历三个步骤 Symbol.toPrimitive -> valueOf -> toString
        在其中一个步骤重写方法即可
  */
  const add = (...args)=>{
    // 获取初始参数,拼接后续参数
      let arr = [...args]
      const fn = function(...args){
          arr =  [...arr,...args]; // arr为参数个数
          return fn // 返回函数本身,保证递归
      }
      // 重写方法
      fn[Symbol.toPrimitive] = ()=> arr.reduce((pre,cur)=>pre+cur);
      return fn
  }
  console.log(+add(1,2,3,4));
  console.log(+add(1,2)(3)(4));
  console.log(+add(1)(2)(3,4,5));

异步函数迭代 -- 众多框架源码的核心思想之一[koa洋葱模型,async/await等]

// 多函数复杂包裹 - 含异步 co + generator
let finalFn = composeForNext(ctx, fns);
// console.log(finalFn)
// finalFn();
// fn1 fn2 fn1 - after-next   fn3  fn2 -after-next

module.exports = composeForNext;

async function fn1(next) {
  console.log("fn1");
  await next();
  console.log("fn1 - end");
}
async function fn2(next) {
  console.log("fn2");
  await delay();
  await next();
  console.log("fn2 - end");
}
async function fn3(next) {
  console.log("fn3");
}
function delay() {
  return new Promise((res) => {
    setTimeout(() => {
      res();
    }, 2000);
  });
}

const fns = [fn1, fn2, fn3];

const composeForNext = (middlewares) => {
  // 返回一个将函数数组同步化的函数(回调地狱)
  // 通过递归及promise,保证函数的执行顺序。
  return function () {
    // ctx上下文
    // 执行递归函数
    return dispatch(0);
    function dispatch(i) {
      // 根据索引获取函数体
      let fn = middlewares[i];
      // 如果无对应函数体,即最后一个next
      if (!fn) {
        // 返回空执行承诺,有可能上一个函数是异步函数,即await的,所以要保证返回的值是promise形式的。
        return Promise.resolve();
      }
      // 如果有函数体
      return Promise.resolve(
        // 函数体执行,并且将下一个函数传入该函数体
        fn(ctx, function next() {
          // 传入ctx上下文到每个函数体
          // 执行下一个函数体,并返回下一个函数的执行承诺,如果不返回则该函数体不会被放置在promise里,则失去同步性。
          return dispatch(i + 1);
        })
      );
    }
  };
};