本文目的
这篇文章会一步一步带你实现一个通用的函数柯里化的函数,即使你完全不懂柯里化这个概念也没关系,我会先介绍柯里化函数的概念,然后以实现一个完整的判断变量类型的函数的例子来让你直观体验函数柯里化,然后再实现一个通用的函数柯里化函数,所以这篇文章至少能带你掌握这两点:
- 实现一个通用的函数柯里化函数
- 借助柯里化实现一个完整的判断变量类型的函数
总体目录
- 函数柯里化的概念
- 柯里化函数初体验
- 实现一个通用的函数柯里化函数
- 小结
函数柯里化概念
首先什么是函数柯里化?得先把概念搞清楚,函数柯里化就是把多个参数的传入转化成 n 个函数依次进行传参。(注意,柯里化一般都要求参数个数是确定的,并且是一个一个传)
觉得有点抽象没关系,下面以一个实现判断变量类型函数的例子来体验下柯里化函数。
首先我们先来思考一下可以用什么方法来判断类型?
- 我们可以用
typeof
来判断类型,但是用 typeof 有缺陷,它无法进一步判断对象,并且typeof null
的值是 object,所以 typeof 我们一般用于判断基础类型; - 除了 typeof 之外,我们可以用
instanceof
来判断谁是谁的实例; - 用
Object.prototype.toString.call
来判断具体类型,返回的是一个字符串; - 通过
constructor
;
那我们就开写,通过Object.prototype.toString.call
,实现一个基础版本:
function isType(val, type) {
return Object.prototype.toString.call(val) === `[object ${type}]`
}
console.log(isType(123, 'String')); // false
console.log(isType(123, 'Number')); // true
但是这么做需要用户手动去写'String'
这些常量,有点风险,你怎么保证用户不会一不小心把'String'
写成'Stirng'
呢?
柯里化函数初体验
怎么解决上面说的那个问题?那我就希望这个isType()
函数能更具体些,形如isString()
isNumber()
之类的。
那么此时柯里化就要登场了,以isString
为例,来直观感受一下柯里化:原来isType
函数是传了两个参数,那么我就要把isString
写成两次调用的形式:
function isString(type) {
return function(val) {
return Object.prototype.toString.call(val) === `[object ${type}]`
}
}
let myIsString = isString('String')
console.log(myIsString(123)); // false
console.log(myIsString('123')); // true
isString('String')
是第一次调用传参,调用完返回一个函数赋值给了myIsString
,随后myIsString()
第二次调用传参123,执行返回结果,这就是函数柯里化的感觉。
如此一来,我们就可以让用户直接通过 myIsString()
来判断是否是字符串了。
要完整判断类型,只判断 String 肯定不够,那接下来就要实现isNumber
isNull
等等,一个一个写? 很麻烦,能不能有个通用的柯里化函数来帮我们完成这件事呢?我只要传String``Number
这些参数就好,而不必自己去手动实现isNumber
等等函数。
下面就来实现这个通用的柯里化函数,然后再解决上面说的这个类型判断的问题。
实现一个通用的函数柯里化函数
实现这个通用柯里化函数的思路:因为我们要把函数fn(fn就是我们希望将其柯里化的函数)的传参,改为一个一个传,并且传一个参数,就返回一个函数,所以我们每次传参的时候我们都要将参数进行记录,当记录的参数个数小于原本函数fn参数的个数时,就返回一个新的函数,如果等于,就真正执行这个函数fn。
看代码:
function curring(fn) {
const inner = (args = []) => {
return args.length >= fn.length ? fn(...args) : (...userArgs) => inner([...args, ...userArgs])
}
return inner()
}
// test
let sum1 = curring(sum)
let sum2 = sum1(1)
let sum3 = sum2(2,3)
let result = sum3(4)
console.log(result); // 10
inner 主要是用来保存传入的参数,存在 args
中,userArgs
代表每次调用新传的参数,如果累积的参数和原函数fn的个数一样,就执行fn,否则就把参数继续存到 args 中。这里面有递归的思想,不熟悉递归的多看多写几遍,好好体会。
上面的这个curring
就是一个通用的实现函数柯里化的函数了,下面我们就用这个通用的柯里化函数来实现我们还未解决的类型判断问题。
function isType(type, val) {
return Object.prototype.toString.call(val) === `[object ${type}]`
}
let util = {};
let typeArr = ['String', 'Number', 'Boolean', 'Null', 'Undefined', 'Symbol', 'Function', 'Array', 'Object', 'Date']
typeArr .forEach(type => {
util[`is${type}`] = curring(isType)(type)
});
// test
console.log(util.isNumber('123')) // false
console.log(util.isNull(null)) // true
小结
第一遍看不懂没关系,下面这段核心代码,一定要结合文章多看几遍,多敲几遍,仔细体会它是如何把 fn 这个函数进行柯里化的。
function curring(fn) {
const inner = (args = []) => {
return args.length >= fn.length ? fn(...args) : (...userArgs) => inner([...args, ...userArgs])
}
return inner()
}