原生js:柯里化与偏函数

70 阅读2分钟

一、什么是柯里化

将一个函数的多个参数变为单个参数的过程叫做柯里化。柯里化后的函数需要多次调用,直到传递的实参个数等于原函数的形参个数时执行

案例:封装一个函数,传入协议、主机名、路径,返回一个地址

    function getUrl(protocol, hostname, pathname) {
      return `${protocol}://${hostname}/${pathname}`
    }

    console.log(getUrl('https', 'taobao.com', 'index')) // https://taobao.com/index
    console.log(getUrl('https', 'jd.com', 'about')) // https://jd.com/about

部分柯里化:

    function getUrl(protocol) {
      return function (hostname, pathname) {
        return `${protocol}://${hostname}/${pathname}`
      }
    }

    const getUrl1 = getUrl('https')

    console.log(getUrl1('taobao.com', 'index')) // https://taobao.com/index
    console.log(getUrl1('jd.com', 'about')) // https://jd.com/about

柯里化:

    function getUrl(protocol) {
      return function (hostname) {
        return function (pathname) {
          return `${protocol}://${hostname}/${pathname}`
        }
      }
    }

    const getUrl1 = getUrl('https')('tabobao.com') // 函数参数得到复用

    console.log(getUrl1('index')) // https://taobao.com/index
    console.log(getUrl1('about')) // https://taobao.com/about

二、柯里化的好处

  1. 函数参数复用
  2. 避免重复判断
  3. 延迟执行,如bind

三、实现一个通用的柯里化函数,将任何一个函数变成柯里化函数

1、使用lodash中的curry

import curry from 'lodash/curry'

    function sum(a, b, c) {
      return a + b + c
    }

    const currySum = curry(sum)

    console.log(currySum(1, 2, 3))
    console.log(currySum(1)(2, 3))
    console.log(currySum(1,2)()()(3))

2、手动实现curry函数

    function sum(a, b, c) {
      return a + b + c
    }

    function myCurry(callback, ...args) {
      if (args.length >= callback.length) {
        return callback(...args)
      }
      return (...rest) => {
        return myCurry(callback, ...args, ...rest)
      }
    }

    const currySum = myCurry(sum)

    console.log(currySum(1, 2, 3))
    console.log(currySum(1)(2, 3))
    console.log(currySum(1, 2)()()(3))

将getUrl函数传入可以得到一个柯里化函数:

    function getUrl(protocol, hostname, pathname) {
      return `${protocol}://${hostname}/${pathname}`
    }

    function myCurry(callback, ...args) {
      if (args.length >= callback.length) {
        return callback(...args)
      }
      return (...rest) => {
        return myCurry(callback, ...args, ...rest)
      }
    }

    const curryGetUrl = myCurry(getUrl)

    console.log(curryGetUrl('http')('taobao.com')('index'))
    console.log(curryGetUrl('http')('taobao.com')('about'))

上面的myCurry是用ES6写的,用ES5如何实现:

      function myCurry(callback) {
        var args = Array.prototype.slice.call(arguments, 1)
        if (args.length >= callback.length) {
          return callback.apply(null, args)
        }
        return function () {
          var rest = Array.prototype.slice.call(arguments)
          var list = [callback].concat(args, rest)
          return myCurry.apply(null, list)
        }
      }

四、偏函数

1、什么是偏函数

一个柯里化后的函数,先调用一次,返回一个新的函数,这个新函数就叫偏函数。

由于调用过一次,可以将一部分参数固定住,这样直接使用偏函数时可以减少重复参数的传递

2、偏函数的使用

  1. 获取数据类型
    const isType = function (type) {
      return function (obj) {
        return Object.prototype.toString.call(obj) === '[object ' + type + ']'
      }
    }

    const isString = isType('String') // 偏函数
    const isNumber = isType('Number') // 偏函数

    console.log(isString('100'))
    console.log(isNumber(100))
  1. 获取标签
    const wrap = function (tag) {
      const startTag = `<${tag}>`
      const endTag = `</${tag}>`
      return (html) => startTag + html + endTag
    }

    const div = wrap('div') // 偏函数
    const h1 = wrap('h1') // 偏函数
    document.write(div('div标签'))
    document.write(h1('h1标签'))

五、反柯里化

1、什么是反柯里化?

反柯里化函数通过指定this的指向,固化一部分参数

2、反柯里化的作用

让一个对象借用原本不属于它的方法

3、对象借用其他对象的方法

一般可以使用call/apply来借用(在构造函数中也可以通过call/apply借用其他类的方法)

      var cat = {
        name: 'Tom',
        getName: function () {
          console.log(this.name)
        }
      }
      var mouse = {
        name: 'Jerry'
      }

      cat.getName.call(mouse) // Jerry

通过uncurry函数将对象中的方法变成公共的,在调用时直接传入对象

      var cat = {
        name: 'Tom',
        age: 8,
        getName: function (...rest) {
          console.log(this.name, rest)
        },
        getAge: function () {
          console.log(this.age)
        }
      }
      var mouse = {
        name: 'Jerry',
        age: 6
      }

      Function.prototype.uncurry = function () {
        var _this = this
        return function () {
          var obj = Array.prototype.shift.call(arguments)
          return _this.apply(obj, arguments)
        }
      }

      var commonGetName = cat.getName.uncurry() // 通过反柯里化函数将getName变为公共的
      var commonGetAge = cat.getAge.uncurry()
      commonGetName(cat, 1)
      commonGetAge(mouse)

其中,uncurry函数可以改写成:

      Function.prototype.uncurry = function () {
        var _this = this
        return function () {
          return Function.prototype.call.apply(_this, arguments)
        }
      }

bind会返回一个偏函数,所以可以使用bind实现:

      Function.prototype.uncurry = function () {
        return Function.prototype.call.bind(this)
      }