JavaScript 内部柯里化

2025-06-07

JavaScript 内部柯里化

在Medium上找到我

柯里化是使用函数时的一种高级技术,它被用在多种编程语言中。

当你将一个接受多个参数的函数分解成一系列嵌套函数时,你就得到了一个curry。每个嵌套函数都会期望该函数的下一个参数。

柯里化函数每次调用都会返回一个新的函数,直到所有参数都已接收完毕。这些参数能够在整个柯里化闭的生命周期内存活,并最终用于执行最终的函数。

一个非常基本的例子看起来像这样:

function combineWords(word) {
  return function(anotherWord) {
    return function(andAnotherWord) {
      return `${word} ${anotherWord} ${andAnotherWord}`
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

要使用此功能,您可以多次调用该函数,直到到达最后一个函数:

const result = combineWords('hello,')('good')('morning')
console.log(result)

// result: 'hello, good morning'
Enter fullscreen mode Exit fullscreen mode

所以实际上,这combineWords是一个柯里化函数(显然),它会等待输入一个单词后再执行序列中的下一个函数。你可以将 绑定'wow!'combineWords一个变量,并复用它创建其他以 开头的问候语'wow!'

let greet = combineWords('wow!')
greet = greet('nice')

console.log(greet('jacket'))
console.log(greet('shoes'))
console.log(greet('eyes'))
console.log(greet('socks'))
console.log(greet('hat'))
console.log(greet('glasses'))
console.log(greet('finger nails'))
console.log(greet('PS3'))
console.log(greet('pet'))

/*
result:
  "wow! nice jacket"
  "wow! nice shoes"
  "wow! nice eyes"
  "wow! nice socks"
  "wow! nice hat"
  "wow! nice glasses"
  "wow! nice finger nails"
  "wow! nice PS3"
  "wow! nice pet"
*/
Enter fullscreen mode Exit fullscreen mode

如果这个概念有点难以理解,请尝试这样阅读:

母亲希望在烹饪之前得到所有 4 个鸡蛋(论点),而她的 4 个孩子将一次一个地将一个鸡蛋带给她。

function Egg() {...}

// the curry func
function prepareCooking(cook) {
  return function(egg1) {
    return function(egg2) {
      return function(egg3) {
        return function(egg4) {
          return cook(egg1, egg2, egg3, egg4)
        }
      }
    }
  }
}

const cook = function(...eggs) {
  api.turnOnStove()
  api.putEggsOnTop(...eggs)
  api.pourSalt()
  api.serve()
  console.log('served children')
  return 'served'
}

const start = prepareCooking(cook)

let collect = start(new Egg())
collect = collect(new Egg())
collect = collect(new Egg())
collect = collect(new Egg()) // this steps into the last function witih argument "egg4" which will invoke the callback passed to "prepareCooking"

// result:  console.log --> "served children"
// collect === 'served'
Enter fullscreen mode Exit fullscreen mode

为了cook调用回调,需要依次传入所有 4 个鸡蛋,每个鸡蛋都预填充等待调用的下一个函数。

如果你在第三个鸡蛋处停下来:

let collect = start(new Egg())
collect = collect(new Egg())
collect = collect(new Egg())
Enter fullscreen mode Exit fullscreen mode

egg4然后,由于尚未达到最后一个函数期望,因此的值collect就是该函数:

function prepareCooking(cook) {
  return function(egg1) {
    return function(egg2) {
      return function(egg3) {
        // HERE
        return function(egg4) {
          return cook(egg1, egg2, egg3, egg4)
        }
      }
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

为了完成咖喱,收集最后一个鸡蛋:

let collect = start(new Egg())
collect = collect(new Egg())
collect = collect(new Egg())
collect = collect(new Egg())

// collect === 'served'
Enter fullscreen mode Exit fullscreen mode

现在,重要的是要知道,每个嵌套函数都拥有 curry 函数内部所有外部作用域的访问权限。了解了这一点,你就可以在各个嵌套函数之间提供自定义逻辑,以适应特定情况。但最好将 curry 函数保留为 curry 函数本身,别再做其他处理。

更高级的 curry 函数如下所示:(我将提供一个ES5版本以及一个,ES6因为有很多旧教程展示了 ES5 语法,这对于较新的 JavaScript 开发人员来说可能有点难以阅读)

ES5

function curry(fn) {
  return function curried() {
    const args = Array.prototype.slice.call(arguments)
    const done = args.length >= fn.length
    if (done) {
      return fn.apply(this, args)
    } else {
      return function() {
        const args2 = Array.prototype.slice.call(arguments)
        return curried.apply(this, args.concat(args2))
      }
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

...与以下内容相同:

ES6

const curry = (fn) => {
  return function curried(...args) {
    const done = args.length >= fn.length
    if (done) {
      return fn.apply(this, args)
    } else {
      return (...args2) => curried.apply(this, [...args, ...args2])
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

让我们更详细地解释一下这个例子:

调用curry(fn)它将返回内部curried函数,该函数将在调用时等待下一个参数。现在,当你调用这个内部函数时,它会评估两个条件:

  1. 调用者传递的参数是否足够满足所有的参数fn
  2. 或者还缺少哪些fn必要的论据?

如果是数字 1,那么我们就有了所有需要fn声明的参数,并且 curry 将通过返回调用fn并传递所有接收到的参数来结束(fn现在基本上正常调用)

然而,如果是第二种情况,那么柯里化必须继续进行,我们必须以某种方式回到内部curried函数,以便继续接收更多参数,直到它满足 的参数要求fn。代码return (...args2) => curried.apply(this, [...args, ...args2])会累积迄今为止暴露的所有参数,并使用它们来继续柯里化。

有一条重要规则:

在等待所有参数收集完毕之前调用的函数必须具有固定数量的参数。这意味着该函数不能包含分散的参数(例如fn(...args):)。

前任:

const curry = (fn) => {
  return function curried(...args) {
    const done = args.length >= fn.length
    if (done) {
      return fn.apply(this, args)
    } else {
      return (...args2) => curried.apply(this, [...args, ...args2])
    }
  }
}

// This is invalid because it uses ...args.  The curry does not understand where to stop
function func(...args) {
  //
}

const currying = curry(func)
Enter fullscreen mode Exit fullscreen mode

结论

我认为柯里化是一项很有趣的技术,因为创建柯里化需要组合其他高级技术,包括闭包、高阶函数和递归。

这篇文章到此结束。希望你能有所收获,并期待未来有更多收获!

在Medium上找到我

文章来源:https://dev.to/jsmanifest/currying-inside-javascript-2gbl
PREV
JavaScript 中的原型模式
NEXT
8 个会毁掉你前途的 ​​JavaScript 初级和中级开发者习惯