最近在读汤姆大叔的系列博文,看到这样一道题,记录下解题过程
题目:实现如下语法的功能:var a = add(2)(3)(4); //9
很显然,要通过闭包来保存中间计算的结果,并通过返回函数进行级联操作,这个返回的函数还应该是一个自引用函数。
由于之前在别的地方看到过类似的题目,记得同时要求满足add(2,3)(4)这样的调用,于是做了下处理。
I
let add = function() {
let args = [];
return function add() {
args = [...args, ...arguments];
console.log(
args.reduce((a,b) => a+b)
);
return add;
}
}();
能运行,同时满足两种调用方式。
但是发现因为这是个IIFE,只初始化了一份,多次调用的结果是累计的。
II
function add2() {
let args = [];
return function add() {
args = [...args, ...arguments];
console.log(args.reduce((a,b) => {
console.log(`${a} + ${b} = ${a+b}`);
return a+b
}));
return add;
}(...arguments)
}
a = add2(2)(3)(4);
b = add2(2,3)(4);
去掉IIFE,每次第一次调用才生成一个新的闭包,可以生成多个实例。
但是通过打印发现,每次级联调用,存在重复计算,
即其实可以只保留上次的运行结果,而不是每次都从头开始计算
III
function add3() {
let args = [];
return function add() {
args = [
[...args, ...arguments].reduce((a,b) => {
console.log(`${a} + ${b} = ${a+b}`);
return a+b
})
];
console.log(args);
return add;
}(...arguments)
}
a = add3(2)(3)(4);
b = add3(2,3)(4);
按照上述的改进,改变了下储存的值,从而减少了计算。
考虑到最近在看设计模式,要求职责分离。
确实可以将add的功能剥离出来,之后如果要求做一个sub(100,20)(30),就可以直接重用了。
IV
function cascade(deal) {
return function() {
let args = [];
return function fn() {
args = [ deal(...args, ...arguments) ];
console.log(args);
return fn;
}(...arguments)
}
}
分离后的cascade接收一个处理业务的回调,这样分离后,感觉重用性好了些。
到这里想不到什么优化点了,于是搜了下其他网友的实现,发现大同小异,但是我忽略了一点:现在的函数结果必须返回一个函数用于级联调用,那么如何获取每次操作的结果呢?
网友的选择是重写了valueOf和toString方法,这样也可以,但是感觉没必要到要重写的地步。
V
function cascade(deal, name) {
name = name || 'res';
return function() {
let args = [];
return function fn() {
args = [ fn[name] = deal(...args, ...arguments) ];
console.log(fn[name]);
return fn;
}(...arguments)
}
}
let add4 = cascade(function() {
return [...arguments].reduce((a,b) => {
console.log(`${a} + ${b} = ${a+b}`);
return a+b
});
});
可以直接在返回的这个自引用函数上挂一个属性来保存每次的操作结果。
最终代码整理成es5,如下
function cascade(deal, name) {
name = name || 'res';
return function() {
var args = [];
return function fn() {
for (var i = 0; i < arguments.length; ++i) {
args.push(arguments[i]);
}
args = [ fn[name] = deal.apply(null, args) ];
return fn;
}.apply(null, arguments)
}
}
var add = cascade(function() {
var res = arguments[0];
for (var i = 1; i < arguments.length; ++i) {
res = res + arguments[i];
}
return res;
});