亲身体验函数的柯里化

这是一篇md格式的文章。。阅读格式美观,请移步我的github 移动端请移步我的博客

 

> 纸上得来终觉浅,绝知此事要躬行。

之前一直在看一些理论的书,每本书上面都会提到函数的 __柯里化(currying)__ ,有时候完全看不懂,有时候看懂了也很快就忘了,因为不知道这种东西有什么用处,所以印象不深。

然后今天再做一个表单验证的插件,中间的关键就是把使用者设置的字典形式的验证规则转化为可以执行的验证函数。例如,插件使用者在HTML中的输入为:

```html
<input type="text" q-validate="{minLength: 6, number: true}">
```

我们从`q-validate`中获取插件使用者设置的验证规则,再稍微转化一下,就得到字典形式的验证规则:

```javascript
el.validateInfo = {
minLength: 6,
number: true
}
```

那么现在我们的 __理想__ 是,把这些验证规则都转化为函数的形式,这样,我们就可以绑定元素的`blur`事件,在它的事件处理函数中依次执行它的验证函数,如果任何一个验证函数返回`false`,就把该元素的`valid`状态设置为`false`。它的代码看起来是这样的:

```javascript

//假设我们已经得到了元素el的验证函数

el.validationRules = {
minLength: function(value){
return value.length > 6 //假装知道是6。。
},
number: function(){
return /^\d+$/.test(value)
}
}

//这样就很容易对el的有效性进行验证了

function validateEl(el){

var rules = el.validationRules

for(var i in rules){
//把每个rule都针对el的value来执行一下
if(!rules[i].call(el, el.value)){
return false
}
}

return true
}

//然后,绑定blur或者submit事件

el.onblur = function(event){
if(!validateEl(el)){
el.valid = false
//做其他事情
}
}
```

思路到这里已经很清楚了,即,

1. 从html中获取验证规则

2. 将验证规则转化为字面量对象的形式

3. 将字面量对象转化为验证函数

4. 绑定元素的事件,当事件发生时,依次执行验证函数,返回`true`或`false`

问题的关键就在于, __对象字面量到验证函数的转化__ 。这里就需要用到 _函数的柯里化_ 。因为我们想要得到的是一个函数,那么说明肯定要用到高阶函数。也就是说,我们应该实现定义好一系列的验证函数,然后从这些函数里根据具体的参数返回具体的验证函数。俗话说得好,

> A line of code is worth a thousand explanations.

假设预先定义的验证函数是这样的,

```javascript

var generalRules = {
minLength: function(arg){
return function(value){
return value > arg
}
}
}

```
那么对于我们的`el`元素来说,就有:

```javascript

el.rules.longerThan6 = generalRules.minLength(6)

```

`generalRules.minLength`这个函数执行以后返回的是一个函数,这个函数在它的闭包中包含了预先设定的参数`6`,那么执行函数时,就会执行与6的比较。这个过程就是 _函数柯里化_ 。所谓的`curry`的就是:

> 只传递给函数一部分参数来调用它,让它返回一个函数去处理剩下的参数。

函数的柯里化有更通用的方法,即:

```javascript
//来自Javascript高级程序设计
function curry(fn){
var args1 = [].slice.call(arguments, 1)
return function(){
var args2 = [].slice.call(arguments)
var finalArgs = args1.concat(args2)

return fn.apply(null, finalArgs)
}
}

```

我们的`minLength`函数用标准的`curry`可以这么写:

```javascript
function minLength(num, value){
return value > num
}

var longerThan5 = curry(minLength, 5)

console.log(longerThan5(6)) //true

//更多的例子
function add(a, b){
return a + b
}

var add2 = curry(add, 2)
add2(3)//5

function test(pattern, str){
return pattern.test(str)
}

var isNumber = curry(test, /\d+/)
isNumber('1') //true

var isText = curry(test, /\w+/)
isText('wasd') //true

```

Javascript高级程序设计的书上给出的这个方法,有一个缺点,就是它假定所有的参数都是等效的,这在简单的`add`函数中可能没什么影响,谁写前写后都不影响结果。但是在后面我写`test`函数是如履薄冰,因为在`curry`函数内部,先传入的参数写在前面,后传入的写在后面,必须对应起来,否则会出错,这样写费心费力。所以对于一般的需求而言,简单地写一个高阶函数就完了,并不一定非要写个`curry`,方法因该用来帮助我们,而不是麻烦我们。前面的`test`函数可以简单地写成:

```javascript

function test(pattern){
return function(value){
return pattern.test(value)
}
}

var isNumber = test(/\d+/)
var isText = test(/\w+/)
isNumber('55') //true
isText('wasd') //true

```
这样写简单明了,易于理解。

posted on 2016-08-24 18:32  CYNICZZZ  阅读(264)  评论(0编辑  收藏  举报

Copyright © Zhoulianjian's Blog 2015