柯里化函数的应用与封装

321 阅读2分钟

本文已参加「新人创作礼」活动,一起开启掘金创造之路。

柯里化(Currying)是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数且返回结果的新函数的技术。

主要应用:

1. 参数复用

  • 案例:URL的拼接
// 拼接URL,复用“protocol”参数
function url(protocol){
  return function(host){
    return `${protocol}${host}`
  }
}
const httpUrl = url("http://") 
const httpsUrl = url("https://")
console.log(httpUrl("192.168.0.2"))       // http://192.168.0.2
console.log(httpsUrl("192.168.0.2"))      // https://192.168.0.2

2. 提前确认(避免每次都重复调用)

  • 绑定dom事件的兼容性处理
// 提前执行这个函数,不用每次执行都会去进行判断
const addEvent = (function () {
  // 高版本浏览器
  if (document.addEventListener) {
    return function (element, event, handler) {
      if (element && event && handler) {
        element.addEventListener(event, handler, false);
      }
    }
  } else { // ie浏览器
    return function (element, event, handler) {
      if (element && event && handler) {
        element.attachEvent('on' + event, handler);
      }
    };
  }
})()

3. 延迟运行

  • 封装函数的bind方法
// context是_bind想要改变的this指向
Function.prototype._bind = function (context) {
  let fn = this       // 调用_bind的对象
  // 返回一个新函数,延迟执行 apply方法
  return function (...args) {
    fn.apply(context, args)
  }
}

经典面试题:add(1)(2)(3)

此题可以网上大多数解法是修改函数的toString方法,但node执行没有效果,浏览器也并非所有都兼容。这里的解决方法是判断参数是否为空,为空就计算,不为空就先push到数组中

function add() {
  let args = [...arguments]
  return function childAdd() {
    if (arguments.length != 0) {  // 判断传入的参数是否是空,不为空就递归
      args.push(...arguments)
      return childAdd
    }
    return args.reduce((pre, item) => pre + item, 0)  // 参数为空就返回所有参数的值
  }
}
console.log(add(1, 2)(4)(5)())

// 附:toString解法
function add() {
  let args = [...arguments]
  return function childAdd() {
    args.push(...arguments)
    childAdd.toString = function () {
      return args.reduce((pre, item) => pre + item, 0)
    }
    return childAdd
  }
}
console.log(add(1, 2)(4)(5))

封装让函数柯里化的方法

// 封装柯里化函数
function curring(fn, ...args) {
  let that = this   // 保存调用curring函数的this
  return function cur() {
    // 收集参数,将首次调用curring传入的参数和这次的参数合并
    let _arg = args.concat(Array.from(arguments))
    if (_arg.length < fn.length) {          // 没有集齐参数,继续收集
      let a = Array.from(arguments)          // 保存本次传入的参数,arguments是类数组
      return function () {
        // 把上一次传入的参数和这一次的合并
        return cur.apply(that, a.concat(Array.from(arguments)))
      }
    }
    return fn.apply(that, _arg)     // 集齐参数,执行该函数
  }
}
// 测试代码
function sum(a, b, c) {
  return a + b + c
}
let fn1 = curring(sum, "jj")
let fn2 = curring(sum)("kkk")
let fn3 = curring(sum)("ccc")('ff')
console.log(fn1(2, 3))   // jj23
console.log(fn1(5)(4))   // jj54
console.log(fn2(2, 3))   // kkk23
console.log(fn2(3)(4))   //kkk34
console.log(fn3(3))     //cccff3
console.log(fn3(4))   // cccff4