JavaScript 内部柯里化
在Medium上找到我
柯里化是使用函数时的一种高级技术,它被用在多种编程语言中。
当你将一个接受多个参数的函数分解成一系列嵌套函数时,你就得到了一个curry。每个嵌套函数都会期望该函数的下一个参数。
柯里化函数每次调用都会返回一个新的函数,直到所有参数都已接收完毕。这些参数能够在整个柯里化闭包的生命周期内存活,并最终用于执行最终的函数。
一个非常基本的例子看起来像这样:
function combineWords(word) {
return function(anotherWord) {
return function(andAnotherWord) {
return `${word} ${anotherWord} ${andAnotherWord}`
}
}
}
要使用此功能,您可以多次调用该函数,直到到达最后一个函数:
const result = combineWords('hello,')('good')('morning')
console.log(result)
// result: 'hello, good morning'
所以实际上,这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"
*/
如果这个概念有点难以理解,请尝试这样阅读:
母亲希望在烹饪之前得到所有 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'
为了cook
调用回调,需要依次传入所有 4 个鸡蛋,每个鸡蛋都预填充等待调用的下一个函数。
如果你在第三个鸡蛋处停下来:
let collect = start(new Egg())
collect = collect(new Egg())
collect = collect(new Egg())
egg4
然后,由于尚未达到最后一个函数期望,因此的值collect
就是该函数:
function prepareCooking(cook) {
return function(egg1) {
return function(egg2) {
return function(egg3) {
// HERE
return function(egg4) {
return cook(egg1, egg2, egg3, egg4)
}
}
}
}
}
为了完成咖喱,收集最后一个鸡蛋:
let collect = start(new Egg())
collect = collect(new Egg())
collect = collect(new Egg())
collect = collect(new Egg())
// collect === 'served'
现在,重要的是要知道,每个嵌套函数都拥有 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))
}
}
}
}
...与以下内容相同:
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])
}
}
}
让我们更详细地解释一下这个例子:
调用curry(fn)
它将返回内部curried
函数,该函数将在调用时等待下一个参数。现在,当你调用这个内部函数时,它会评估两个条件:
- 调用者传递的参数是否足够满足所有的参数
fn
? - 或者还缺少哪些
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)
结论
我认为柯里化是一项很有趣的技术,因为创建柯里化需要组合其他高级技术,包括闭包、高阶函数和递归。
这篇文章到此结束。希望你能有所收获,并期待未来有更多收获!
在Medium上找到我
文章来源:https://dev.to/jsmanifest/currying-inside-javascript-2gbl