熟悉一下常见的高阶函数

beforeSay

// 之前实现的业务代码,现在需要扩展当前的业务
function say(a, b) {
    console.log('say', a, b);
}

// 给 say 方法,添加一个方法,在 say 执行之前调用
Function.prototype.before = function (callback) {
    return (...args) => {
        callback();
        this(...args);
    };
}

let beforeSay = say.before(function () {
    // 此处是扩展的业务代码
    console.log('before say');
})
beforeSay('hello', 'world');

单例设计模式

// 利用闭包的保护机制,避免变量全局污染
let weatherModule = (function () {
    let _default = 'beijing';

    let queryWeather = function () { };
    let setWeather = function () { };

    return {
        // ES6 setWeather: setWeather
        setWeather
    }
})();

惰性函数

  1. 普通事件绑定

    /**
     * 兼容性 事件绑定 
     * @param {*} ele dom 节点
     * @param {*} type 事件类型
     * @param {*} func 触发函数
     */
    function emit(ele, type, func) {
      if (ele.addEventListener) {
          // dom2 事件 IE8 以上
          ele.addEventListener(type, func);
      } else if (ele.attachEvent) {
          // dom2 事件 IE8 以下
          ele.attachEvent(type, func);
      } else {
          // dom0 事件 
          ele['on' + type] = func;
      }
    }
    
    //调用 2 次,需要重复判断
    emit(box, 'click', fn1);
    emit(box, 'click', fn2);
    
  2. 惰性思想实现

    function emit(ele, type, func) {
      if (ele.addEventListener) {
          // 判断是该事件类型后,直接重写 emit函数
          emit = function (ele, type, func) {
              ele.addEventListener(type, func, false);
          }
      } else if (ele.attachEvent) {
          emit = function (ele, type, func) {
              ele.attachEvent(type, func);
          }
      } else {
          emit = function (ele, type, func) {
              ele['on' + type] = func;
          }
      }
    	
      // 调用emit,完成事件绑定
      emit(ele, type, func);
    }
    
    //调用 2 次,第二次不会再重复判断,惰性思想,优化了性能
    emit(box, 'click', fn1);
    emit(box, 'click', fn2);
    

手写 bind

JavaScript
JavaScript
// ES5 实现
~function (proto) {
    function bind(context) {
        context = context || window;
        var outerArgs = Array.prototype.slice.call(arguments, 1);
        // this: 要处理的函数
        var _this = this;
        return function proxy() {
            var innerArgs = Array.prototype.slice.call(arguments, 0);
            _this.apply(context, outerArgs.concat(innerArgs));
        }
    }
    proto.bind = bind;
}(Function.prototype);
// ES6 实现
~function (proto) {
    function bind(context = window, ...outerArgs) {
        // this: 要处理的函数
        let _this = this;
        return function proxy(...innerArgs) {
            _this.call(context, ...outerArgs.concat(innerArgs));
        }
    }
    proto.bind = bind;
}(Function.prototype);

柯理化(Currying)函数

  1. 柯里化是编程语言中的一个通用的概念,是指把接收多个参数的函数变换成接收单一参数的函数,嵌套返回直到所有参数都被使用并返回最终结果,柯里化不会调用函数,它只是对函数进行转换;

  2. 应用及特点:

    1. 参数复用:当在多次调用同一个函数,并且传递的参数绝大多数是相同的,那么就可以用柯里化进行函数的复用;
    2. 提前返回:多次调用内部判断,可以直接把第一次判断的结果返回外部接收;
    3. 延迟执行:避免重复的去执行程序,等真正需要结果的时候再执行;
  1. 实现效果:

    add(1);       // 1
    add(1)(2);    // 3
    add(1)(2)(3); // 6
    add(1)(2, 3); // 6
    add(1, 2)(3); // 6
    add(1, 2, 3); // 6
    
  2. 实现代码:

    JavaScript
    JavaScript
    /*
     * 实现方案一 
     */
    function add(...outerArgs) {
        // 重写了 ADD,第二次之后将所有参数全部放到数组中
        add = function (...innerArgs) {
            outerArgs.push(...innerArgs);
            return add;
        };
        add.toString = function () {
            return outerArgs.reduce((x, y) => x + y);
        };
        return add;
    }
    let res = add(1, 2)(3)(4)(5)(6, 7);
    alert(res);  //=>alert会把输出的值转换为字符串(toString())
    /*
     * 第一次执行 ADD  outerArgs=[1,2]  重写了ADD
     * 第二次执行 ADD  innerArgs=[3]   outerArgs=[1,2,3]
     * 第三次执行 ADD  innerArgs=[4]   outerArgs=[1,2,3,4]
     * ......
     * outerArgs=[1,2,3,4,5,6,7]
     */
    // console.log(res.toString());
    
    /*
     * 实现方案二 :currying(柯理化函数封装+递归)
     */
    function currying(anonymous, length) {
        // 直接返回 add 函数
        return function add(...args) {
            // 递归终止条件
            if (args.length == length) {
                return anonymous(...args);
            }
    
            return currying(anonymous.bind(null, ...args), length - args.length);
        }
    }
    let add = currying(function anonymous(...args) {
        return args.reduce((x, y) => x + y);
    }, 4);
    /*
     * AO(currying) 
     *   anonymous=求和函数
     *   length=4
     *
     * ADD第一次执行  args=[1,2] 
     *   currying第二次执行
     *    anonymous=求和函数 预先传递的参数[1,2]
     *      length=2
     *   ADD第二次执行 args=[3]
     *      currying第三次执行 
     *        anonymous=求和函数 预先传递的参数[3]
     *        length=1
     *      ADD函数第三次执行 args=[4]
     *        把上一次的求和函数执行(4)
     *   弊端:一层一层形成多次闭包
     */
    console.log(add(1, 2)(3)(4));
    

管道(pipe)函数

管道(pipe)函数也叫函数扁平化:

  1. 后一个函数作为前一个函数的参数;
  2. 最后一个函数可以接受多个参数,前面的函数只能接受单个参数;
  3. 后一个的返回值传给前一个;

前置知识

  1. reduce 的语法

    /**
     * callback 回调函数
     *  - accumulator 累计器
     *  - currentValue 当前值
     *  - index 当前索引
     *  - array 调用 reduce 的数组
     * initialValue 作为第一次调用 callback 函数时的第一个参数的值,如果没有提供初始值,则将使用数组中的第一个元素
     */
    arr.reduce(callback(accumulator, currentValue, index, array), initialValue)
    
  2. 使用 reduce 的第一个参数,每次调用的参数和返回值如下表:

    [0, 1, 2, 3, 4].reduce(function (accumulator, currentValue, currentIndex, array) {
      return accumulator + currentValue;
    });
    
    callback accumulator currVal Index array return value
    第一次调用 0 1 1 [0, 1, 2, 3, 4] 1
    第二次调用 1 2 2 [0, 1, 2, 3, 4] 3
    第三次调用 3 3 3 [0, 1, 2, 3, 4] 6
    第四次调用 6 4 4 [0, 1, 2, 3, 4] 10
  3. 提供一个初始值作为 reduce 方法的第二个参数,每次调用的参数和返回值如下表:

    [0, 1, 2, 3, 4].reduce((accumulator, currentValue, currentIndex, array) => {
      return accumulator + currentValue
    }, 10)
    
    callback accumulator currVal Index array return value
    第一次调用 10 0 0 [0, 1, 2, 3, 4] 10
    第二次调用 10 1 1 [0, 1, 2, 3, 4] 11
    第三次调用 11 2 2 [0, 1, 2, 3, 4] 13
    第四次调用 13 3 3 [0, 1, 2, 3, 4] 16
    第五次调用 16 4 4 [0, 1, 2, 3, 4] 20

代码实现

  1. 未实现扁平化

    let fn1 = function (x) {
      return x + 10;
    }
    let fn2 = function (x) {
      return x * 10;
    }
    let fn3 = function (x) {
      return x / 10;
    }
    // 输出16
    console.log(fn3(fn1(fn2(fn1(5)))));
    
  2. 实现函数扁平化

    JavaScript
    JavaScript
    // 使用 reduce 实现扁平化(实现的是,参数从前到后计算)
    /**
     * 手写compose
     * @param {*} funcs 函数集合
     * @return {*} 返回一个代理函数
     */
    function compose(...funcs) {
      return function proxy(...args) {
        // 第一次调用传递的参数集合
        let len = funcs.length;
    
        if (len === 0) {
          // 一个函数都不传,不需要执行,返回args
          // compose()(5) :args 为 5
          return args;
        }
    
        if (len === 1) {
          return funcs[0](...args);
        }
    
        // 两个及以上的函数
        return funcs.reduce((fun, y) => {
          console.log(fun, y)
          // [Function: fn1] [Function: fn2]
          // 150             [Function: fn1]
          // 160             [Function: fn3]
          // 16
          // 第一次执行的时候 fun 是函数,之后都是执行函数出来的结果赋值给 fun 
          return typeof fun === 'function' ? y(fun(...args)) : y(fun);
        });
      }
    }
    console.log(compose()(5));// =>5
    console.log(compose(fn1)(5));// =>5+10=15
    console.log(compose(fn1, fn2)(5));// =>5+10=15 fn2(15)=150...
    console.log(compose(fn1, fn2, fn1, fn3)(5));// =>16
    
    // redux 使用 reduce 实现函数扁平化(实现的是,参数从后到前计算)
    function compose(...funcs) {
      //没有传入函数参数,就返回一个默认函数(直接返回参数)
      if (funcs.length === 0) {
        // compose()(5) :args 为 5
        return arg => arg
        // return function (arg) {
        //   return  arg;
        // }
      }
    	
      if (funcs.length === 1) {
        // 单元素 数组时调用 reduce,会直接返回该元素,不会执行  callback,所以这里手动执行
        // compose(fn1)(5)
        return funcs[0]
      }
    	
      // 依次拼凑执行函数
      // reduce不传第二个参数:a初始值是数组第一项的值,然后每一次返回的结果作为下一次的值
      return funcs.reduce(function (a, b) {
        console.log(a, b);
        // [Function: fn1]        [Function: fn2]
        // fn1(fn2(...args))      [Function: fn1]
        // fn1(fn2(fn1(...args))) [Function: fn3]
        // fn1(fn2(fn1(fn3(...args))))
        return function (...args) {
          return a(b(...args))
        }
      })
      // 等价于:
      // return funcs.reduce((a, b) => (...args) => a(b(...args)))
    }
    	
    // fn1(fn2(fn1(fn3(...args))))
    console.log(compose(fn1, fn2, fn1, fn3)(5));// =>115
    
打赏作者
您的打赏是我前进的动力
微信
支付宝
评论

中午好👏🏻,我是 ✍🏻   疯狂 codding 中...

粽子

这有关于前端开发的技术文档和你分享。

相信你可以在这里找到对你有用的知识和教程。

了解更多

目录

  1. 1. 熟悉一下常见的高阶函数
    1. 1.1. beforeSay
    2. 1.2. 单例设计模式
    3. 1.3. 惰性函数
    4. 1.4. 手写 bind
  2. 2. 柯理化(Currying)函数
  3. 3. 管道(pipe)函数
    1. 3.1. 前置知识
    2. 3.2. 代码实现