定义

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

来源: 维基百科

说白了就是返回函数的函数,其实在开发过程中多半已经用过了,只是之前没有去了解过这个东西叫什么。

我认为,他还可以是被看为一种轻量级的面向对象。

示例

定义一个getAdder函数,返回一个adder

1
2
3
4
5
6
7
8
function getAdder(n) {
return m => n + m
}

const adder = getAdder(3)

console.log(adder(4))
// 7

这便是一种柯里化的应用了。

实际问题

我在开发过程中遇到过这种需求,也就是你现在看到的页面,它是用markdown写成的,并且文件头部有一些元信息,即metadata,本质是一堆键值对,用来描述这个文件。比如当前文件的头部就是这样的:

1
2
3
4
5
date: 2022-5-14
category: Learn
tags: JS TS
intro: 返回函数的函数,可以认为是轻量级面向对象
id: 11

我把这个结构抽象一下,改成下面这样:

1
2
foo: abc
bar: def

我们的需求就是读取出来这些键值对。

解决方案

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function parser(metadata) {
return key => {
const prefix = key + ':'
const data = metadata.find(s => s.startsWith(prefix))

return data.substring(prefix.length).trim()
}
}

const parse = parser([
'foo: abc',
'bar: def'
])

console.log(parse('foo'))
// abc

console.log(parse('bar'))
// def

可以看出,这里的parser函数就是保存了metadata这一变量内容并且重复使用。

针对于parser函数的执行效率,可以进行以下优化:

1
2
3
4
5
6
7
8
function parser(metadata) {
const map = new Map()
for (const data of metadata) {
const [key, value] = data.split(/\s*:\s*/)
map.set(key, value)
}
return key => map.get(key)
}

这里提前把所有的键值对都保存下来了,到执行的时候只需要获取一下即可。

那么回到开头说的,他可以被看成一种轻量级的面向对象,所以说完全可以用到面向对象的方法实现这个功能:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class Parser {
metadata = new Map()

constructor(metadata) {
for (const data of metadata) {
const [key, value] = data.split(/\s*:\s*/)
this.metadata.set(key, value)
}
}

parse(key) {
return this.metadata.get(key)
}
}

const metadata = [
'foo: abc',
'bar: def'
]

const parser = new Parser(metadata)

console.log(parser.parse('foo'))
// abc

console.log(parser.parse('bar'))
// def

但是实际应用中还是通过柯里化的方法更简洁明了,这也正体现这JS这门语言的魅力: 提供多种解决手段,我们总能从中找到较优的解决方案。