定义
柯里化是指将一个函数分解为一系列函数的过程,每个函数都只接收一个参数
函数柯里化,英语:Currying,高阶函数的一个特殊用法。是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数的技术
一个简单的例子:
1 2 3 4 5 6 7 8 9 10
| function add(a, b) { return a + b } add(1, 5) function curryingadd(x) { return function(y) { return x + y } } curryadd(1)(5)
|
柯里化的用途
参数复用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
|
function check(reg, txt) { return reg.test(txt) }
check(/\d+/g, 'test') check(/[a-z]+/g, 'test')
function curryingCheck(reg) { return function(txt) { return reg.test(txt) } }
var hasNumber = curryingCheck(/\d+/g) var hasLetter = curryingCheck(/[a-z]+/g)
hasNumber('test1') hasNumber('testtest') hasLetter('21212')
|
如果有很多地方都要校验是否有数字,如果用 check 函数,得反复传递第一个参数;curryingCheck 能将第一个参数 reg 进行复用,这样别的地方就能够直接调用 hasNumber,hasLetter 等函数,让参数能够复用,调用起来也更方便
提前确认
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 28 29 30 31 32 33 34 35 36 37
| var on = function(element, event, handler) { if (document.addEventListener) { if (element && event && handler) { element.addEventListener(event, handler, false) } } else { if (element && event && handler) { element.attachEvent('on' + event, handler) } } }
var on = (function() { if (document.addEventListener) { return function(element, event, handler) { if (element && event && handler) { element.addEventListener(event, handler, false) } } } else { return function(element, event, handler) { if (element && event && handler) { element.attachEvent('on' + event, handler) } } } })()
var on = function(isSupport, element, event, handler) { isSupport = isSupport || document.addEventListener if (isSupport) { return element.addEventListener(event, handler, false) } else { return element.attachEvent('on' + event, handler) } }
|
我们在做项目的过程中,封装一些 dom 操作可以说再常见不过,上面第一种写法也是比较常见,但是我们看看第二种写法,它相对一第一种写法就是自执行然后返回一个新的函数,这样其实就是提前确定了会走哪一个方法,避免每次都进行判断
延迟运行
1 2 3 4 5 6 7
| Function.prototype.bind = function(context) { var _this = this var args = [...arguments].slice(1) return function() { return _this.apply(context, args.concat(...arguments)) } }
|
bind 实现的机制就是 Currying,虽然上面这个 bind 有点简单没有考虑所有情况
柯里化的封装
其实和之前写的 bind 的实现很像
1 2 3 4 5 6 7 8 9
| var currying = function(fn) { var args = [...arguments].slice(1) return function() { return fn.apply(this, args.concat(...arguments)) } }
|
这样返回的话其实只能多扩展一个参数,currying(a)(b)(c)的情况无法支持,所以需要递归
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| function progressCurrying(fn, ...args) { var _this = this var len = fn.length var args = args || []
return function() { var _args = [...arguments] args.push(...arguments)
if (_args.length < len) { return progressCurrying.call(_this, fn, _args) }
return fn.apply(this, args) } }
|
一道经典面试题
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| add(1)(2)(3) = 6 add(1, 2, 3)(4) = 10 add(1)(2)(3)(4)(5) = 15
function add() { let _args = [...arguments] let _result = function() { _args.push(...arguments) return _result } _result.toString = function() { return _args.reduce((acc, curr) => acc + curr, 0) } return _result }
add(1)(2)(3) add(1, 2, 3)(4) add(1)(2)(3)(4)(5) add(2, 6)(1)
|
toString 或 valueOf 的改写
上面那道题的的 toString 的改写也是非常有意思的一个知识点
当变量对象(函数也是对象)遇到一些情况的时候,会偷偷地调用 toString 和 valueOf 方法(隐式调用),那我们就可以改写这两个方法做一些事
这些情况会触发隐式调用:
- 加减乘除
- 比较运算符>
- ==
- String()
- Number()
两个都重写的情况:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| var obj = { i: 10, valueOf: function() { console.log('执行了valueOf()') return this.i + 20 }, toString: function() { console.log('执行了toString()') return this.valueOf() + 20 } } console.log(+obj) console.log(obj > 40) console.log(obj == 30)
|
只重写了 toString
1 2 3 4 5 6 7 8 9 10 11 12
| var aa = { i: 10, toString: function() { console.log('toString') return this.i } } console.log(+aa) console.log('' + aa) console.log(String(aa)) console.log(Number(aa)) console.log(aa == '10')
|
只重写了 valueOf
1 2 3 4 5 6 7 8 9 10 11 12
| var aa = { i: 10, valueOf: function() { console.log('valueOf') return this.i } } console.log(+aa) console.log('' + aa) console.log(String(aa)) console.log(Number(aa)) console.log(aa == '10')
|
结论:如果只重写了 toString,对象转换时会无视 valueOf 的存在来进行转换。但是,如果只重写了 valueOf 方法,在要转换为字符串的时候会优先考虑 valueOf 方法。在不能调用 toString 的情况下,只能让 valueOf 上阵了
当两者都重写的时候,优先 valueOf
结论二:所以上面那道面试题,是触发了隐式调用,偷偷地执行了重写的toString方法。虽然还是没懂是怎么触发隐式调用的好像都不是我所知的那几种情况0.0
参考资料
张鑫旭-JS 中的柯里
详解 JS 函数柯里化
JavaScript 中 valueOf、toString 的隐式调用
JavaScript 中 valueOf 函数与 toString 方法深入理解