柯里化的定义
红宝书(第3版):用于创建已经设置好了一个或多个参数的函数。基本方法是使用一个闭包返回一个函数。(P604)
维基百科:柯里化(英语:Currying),是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数的技术。
柯里化的应用
柯里化有 3 个常见应用:
1、参数复用 – 当在多次调用同一个函数,并且传递的参数绝大多数是相同的,那么该函数可能是一个很好的柯里化候选
2、提前返回 – 多次调用多次内部判断,可以直接把第一次判断的结果返回外部接收
3、延迟计算/运行 – 避免重复的去执行程序,等真正需要结果的时候再执行
应用一:参数复用
uri拼接
function uri_curring(protocol) {
return function(hostname, pathname) {
return `${protocol}${hostname}${pathname}`;
}
}
// 测试一下
const uri_https = uri_curring('https://');
const uri1 = uri_https('www.fedbook.cn', '/frontend-languages/javascript/function-currying/');
const uri2 = uri_https('www.fedbook.cn', '/handwritten/javascript/10-实现bind方法/');
const uri3 = uri_https('www.wenyuanblog.com', '/');
console.log(uri1);
console.log(uri2);
console.log(uri3);
正则封装
// 正常正则验证字符串 reg.test(txt)
// 函数封装后
function check(reg, txt) {
return reg.test(txt)
}
check(/\d+/g, 'test') //false
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)
hasNumber('test1') // true
hasNumber('testtest') // false
hasLetter('21212') // false
提前确认
// 使用立即执行函数,当我们把这个函数放在文件的头部,就可以先进行执行判断
const addEvent = (function() {
if(window.addEventListener) {
console.log('判断为其它浏览器')
return function(element, type, listener, useCapture) {
element.addEventListener(type, function(e) {
listener.call(element, e);
}, useCapture);
}
} else if(window.attachEvent) {
console.log('判断为 IE9 以下浏览器')
return function(element, type, handler) {
element.attachEvent('on'+type, function(e) {
handler.call(element, e);
});
}
}}) ();
// 测试一下
let div = document.querySelector('div');
let p = document.querySelector('p');
let span = document.querySelector('span');
addEvent(div, 'click', (e) => {console.log('点击了 div');}, true);
addEvent(p, 'click', (e) => {console.log('点击了 p');}, true);
addEvent(span, 'click', (e) => {console.log('点击了 span');}, true);
// 触发多次事件也依旧只会触发一次 if 条件判断。
面试题
add(1)(2)(3) = 6;
add(1, 2, 3)(4) = 10;
add(1)(2)(3)(4)(5) = 15;
function add() {
// 将传入的不定参数转为数组对象
let args = Array.prototype.slice.call(arguments);
// 递归:内部函数里面进行自己调用自己
// 当 add 函数不断调用时,把第 N+1 个括号的参数加入到第 N 个括号的参数里面
let inner = function() {
args.push(...arguments);
return inner;
}
inner.toString = function() {
// args 里的值不断累加
return args.reduce(function(prev, cur) {
return prev + cur;
});
};
return inner;
}
// 测试一下
let result = add(1)(2)(3)(4);
console.log(result);
1)不定参数 arguments 需要转为数组对象:
因为 arguments 并不是真正的数组,而是与数组类似对象,Array.prototype.slice.call(arguments) 能将具有 length 属性的对象转成数组。
2)toString 隐形转换的特性:
对于 add(1)(2)(3)(4),执行每个括号的时候都返回 inner 函数,不断自己调用自己,每次内部函数返回的都是内部函数。
如果打印函数执行的最终返回结果,可以发现返回了一个字符串(原本的函数被转换为字符串返回了),这即是发生了隐式转换,而发生隐式转换是因为调用了内部的 toString 方法。
知道了这一点,我们就可以利用这个特性自定义返回的内容:重写 inner 函数的 toString 方法,在里面实现参数相加的执行代码。
值得一提的是,这种处理后能够返回正确的累加结果,但返回的结果是个函数类型(function),这是因为我们在用递归返回函数,内部函数在被隐式转换为字符串之前本来就是一个函数。