函数柯里化

Wed, August 7, 2024 - 7 min read

什么是函数柯里化

在计算机科学中,柯里化,是将一个多参数函数转换为一系列单参数函数。在柯里化过程中,一个接受多个参数的函数被转换成一个接受单个参数的函数,这个单参数函数返回另一个函数,后者再接受下一个参数,以此类推,直到所有参数都被提供,最终执行原函数。

柯里化快速入门

假设我们有一个求取两个数之和的函数:

function add(a, b) {
	return a + b
}
 
console.log(add(1, 2))
console.log(add(3, 4))

在上面的示例中,我们有一个 add 函数,接收两个形参,返回两形参的和。

在调用的时候,我们每次也需要传递两个参数。

现在,我们对其进行柯里化,如下:

function add(a) {
	return function(b) {
		return a + b
	}
}
 
console.log(add(1)(2))
console.log(add(3)(4))

在上面的代码中,我们对 add 函数进行了柯里化改造,只接受一个参数,但是返回的也不是值了,而是返回一个函数,这个函数也接收一个参数,然后利用闭包的特性,可以访问到最开始传入的 x 的值,最终返回 xy 的和。

所以,通过上面的这个示例,我们能够体会到前面所说的柯里化函数的特点:

一个柯里化的函数首先会接受一些参数,接受了这些参数之后,该函数并不会立即求值,而是继续返回另外一个函数,刚才传入的参数在函数形成的闭包中被保存起来。待到函数被真正需要求值的时候,之前传入的所有参数都会被一次性用于求值。

函数柯里化实际应用

柯里化最大的好处就是参数复用

应用场景示例:

下方是一个正则的校验函数,正常来说直接调用 check 函数就可以了,但是如果我有很多地方都要校验是否有数字,其实就是需要将第一个参数 reg 进行复用,这样别的地方就能够直接调用 hasNumber、hasLetter 等函数,让参数能够复用,调用起来也更方便。

// 正常正则验证字符串 reg.test(txt)
 
// 函数封装后
function check(reg, txt) {
    return reg.test(txt)
}
 
// 即使是相同的正则表达式,也需要重新传递一次
console.log(check(/\d+/g, 'test1')); // true
console.log(check(/\d+/g, 'testtest')); // false
console.log(check(/[a-z]+/g, 'test')); // true
 
// Currying后
function curryingCheck(reg) {
    return function (txt) {
        return reg.test(txt)
    }
}
 
// 正则表达式通过闭包保存了起来
var hasNumber = curryingCheck(/\d+/g)
var hasLetter = curryingCheck(/[a-z]+/g)
 
console.log(hasNumber('test1')); // true
console.log(hasNumber('testtest'));  // false
console.log(hasLetter('21212')); // false

经典柯里化面试题

实现一个 add 方法,使计算结果能够满足如下预期:

add(1)(2)(3) = 6;
add(1, 2, 3)(4) = 10;
add(1)(2)(3)(4)(5) = 15;

要完成上面的需求,我们就可以使用柯里化函数:

function add(...args) {
    // 第一次执行时,定义一个数组专门用来存储所有的参数
	const _args = args.slice()
 
    // 在内部声明一个函数,利用闭包的特性保存 _args 并收集所有的参数值
    const _adder = function () {
        _args.push(...arguments);
        return _adder;
    };
 
    // 利用 toString 隐式转换的特性,当最后执行时显式或隐式调用这个函数,返回 _args 的累加和
    _adder.toString = function () {
        return _args.reduce((pre, cur) => pre + cur);
    }
 
    // 这个 return 是第一次调用的时候返回上面的函数体,
    // 这样后面所有的括号再执行的时候就是执行 _adder 函数体
    return _adder;
}
 
// 我们可以显式或隐式调用这个函数
// 1 显式使用 toString() 方法
 console.log(add(1)(2)(3).toString()); // 输出数字: 6
console.log(add(1, 2, 3)(4).toString()); // 输出数字: 10
// 2 隐式转换自动调用 toString() 方法
console.log(''+add(1)(2)(3)(4)(5)); // 输出字符串: '15'
console.log(String(add(2, 6)(1))); // 输出字符串: '9'
alert(add(1, 2, 3)(4)) // 输出字符串: '10'

toString方法和隐式类型转换

toString方法

在 JavaScript 中,toString 方法是一个对象的默认方法,当对象被转换为字符串时,JavaScript 引擎会自动调用该对象的 toString 方法。你可以通过重写对象的 toString 方法来定义对象的字符串表示形式。

隐式类型转换

  1. 简介:JavaScript 中的类型转换是自动发生的。隐式类型转换通常出现在使用操作符(如加法运算符 +)时,操作数的类型不完全匹配。在这些情况下,JavaScript 引擎会自动将操作数转换为适当的类型以执行操作。

  2. 转换为字符串:当你使用 alert 或触发对象到字符串的隐式转换时,如果传递的是一个对象,JavaScript 会隐式地调用该对象的 toString 方法,并将其作为字符串输出。

  3. String 函数String 函数也可以将对象转换为字符串。