JS提高--认识函数柯里化

284 阅读1分钟

学习参考文章1

学习参考文章2

开头放个柯里化的通用方法

// 支持多参数传递
function progressCurrying(fn, args) {

    var _this = this
    var len = fn.length;
    var args = args || [];

    return function() {
        var _args = Array.prototype.slice.call(arguments);
        Array.prototype.push.apply(args, _args);

        // 如果参数个数小于最初的fn.length,则递归调用,继续收集参数
        if (_args.length < len) {
            return progressCurrying.call(_this, fn, _args);
        }

        // 参数收集完毕,则执行fn
        return fn.apply(this, _args);
    }
}

定义

  • 把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数的技术

  • 解释:只传递一个参数来调用函数,返回一个函数来接收剩余参数并处理结果(也被称为部分求值)

    • 原函数--接收4个参数,return 结果
    • 柯里化--接收1个参数,return一个函数
      • 此函数接收剩余参数并return最终结果
  • 例子:将原本传递x、y的函数变成两个函数,外层传递x内层传递y

function curryingAdd(x) {
    return function (y) {
        return x + y
    }
}

应用

(1)参数复用

//正常方法
function meet(msg, name) {
  console.log(name, msg, "!");
}
meet("hello", "Bob");

//柯里化封装
function meetCur(msg) {
  return function (name) {
    console.log(name, msg, "!");
  };
}

//获得自动填充hello的方法
let helloMeet = meetCur("hello");
helloMeet("Lucy");
helloMeet("Bob");
helloMeet("Jack");

//获得自动填充goodBye的方法
let byeMeet = meetCur("goodBye");
byeMeet("Lucy");
byeMeet("Bob");
byeMeet("Jack");

(2)提前判断

原本每次调用on都要判断是否支持addEventListener,

柯里化后在赋值时确认后就不需要再每次判断

var on = function(element, event, handler) {
    if (document.addEventListener) {
        if (element && event && handler) {
            element.addEventListener(event, handler, false);
        }
    } else {
        if (element && event && handler) {
            element.attachEvent('on' + event, handler);
        }
    }
}

var on = (function() {
    if (document.addEventListener) {
        return function(element, event, handler) {
            if (element && event && handler) {
                element.addEventListener(event, handler, false);
            }
        };
    } else {
        return function(element, event, handler) {
            if (element && event && handler) {
                element.attachEvent('on' + event, handler);
            }
        };
    }
})();

(3)bind实现

<script>
      Function.prototype.mybind = function (context) {
        let that = this;
        // console.log("tha1t", that);
        let args = Array.prototype.slice.call(arguments, 1);
        console.log(args, "args");
        return function () {
          //   console.log(that, "--that", str);
            let arr = Array.prototype.concat.apply(args, arguments);
          return that.apply(context, arr);
        };
      };

      function tt(...str) {
        // console.log(this, "------", str);
        return str;
      }
      let ll = console.log;
      console.log(tt.mybind(this, 234, 234, 43)("0--00"));
      ll.mybind(this, 234, 234, 43)("0--00")
      
    </script>

(4)延迟执行

下面代码,只要持续传入参数就不执行处理结果而是接收参数并返回函数。

当不再传入参数时就将之前接收的所有参数用于执行,返回执行结果

var curryScore = function (fn) {
  var allScore = []; //用来存取每次输入的单个值
  // 这些用来预处理
  return function () {
    if (arguments.length === 0) {
      //不再传入参数时执行fn
      fn.apply(null, allScore);
    } else {
      //传入参数则保存起来
      allScore = allScore.concat([].slice.call(arguments));
    }
  };
};
var result = 0;
// addScroe得到的是一个function
var addScore = curryScore(function () {
  //fn
  for (var i = 0; i < arguments.length; i++) {
    result += arguments[i];
  }
});

addScore(3);
console.log(result); //0
addScore(3);
console.log(result); //0
addScore(3);
console.log(result); //0
addScore();
console.log(result); //9

封装通用方法

  • 完整的,通用的,将一个函数封装柯里化

解析

// 支持多参数传递
function progressCurrying(fn, args) {

    var _this = this
    var len = fn.length; //fn声明中的形参数量
    var args = args || []; //没有传args则设为[]

    return function() {
        //获取之后传入的参数,将参数整入数组
        var _args = Array.prototype.slice.call(arguments);
        //将之前传入的参数与这次传入的参数整合
        Array.prototype.push.apply(args, _args);

        // 如果参数个数小于最初的fn.length,则递归调用,继续收集参数
        if (_args.length < len) {
            //即return的仍然是一个可加()执行的。
            return progressCurrying.call(_this, fn, _args);
        }

        // 参数收集完毕,则执行fn
        return fn.apply(this, _args);
    }
}

练习题

  • 简化题
//实现一个add方法,使其实现下面结果
//(将网上那道面试题简化了一下)

console.log(add(1)(2)()); //3
console.log(add(1, 2, 3)(4)()); //10
console.log(add(1)(2)(3)(4)(5)()); //15
function add(...args) {
  let ret = [];
  ret.push(...args);
  if (args.length == 0) return adder();
  function adder(...arg2) {
    ret.push(...arg2);
    if (arg2.length === 0) {
      let sum = 0;
      ret.forEach((item) => {
        sum += item;
      });
      return sum;
    } else {
      return adder;
    }
  }
  return adder;
}
  • 原题
// 实现一个add方法,使计算结果能够满足如下预期:
            // add(1)(2)(3) = 6;
            // add(1, 2, 3)= 6;
            // add(1)(2,3)= 6;

function add() {
  var _args = Array.prototype.slice.apply(arguments);
  // 每次_add传入的参数都保存到_args中
  // 这里体现了 柯里化 参数复用 (_args)与 延迟执行(等到所有参数收集完毕再进行计算)的两大特点
  var _add = function () {
    Array.prototype.slice.apply(arguments).forEach(function (item) {
      // 另一个特性,在最终结算结果之前提前确认参数,
      if (!isNaN(item)) {
        _args.push(item);
      }
    });
    return _add;
  };
  // 利用toString隐式转换的特性,当最后执行时隐式转换,并计算最终的值返回
    //这一步解决同时可以加()执行也可以输出结果问题
  _add.toString = function () {
    return _args.reduce(function (x, y) {
      return x + y;
    });
  };
  return _add;
}