谈谈 JavaScript 中形如
add(1)(2)(3)(4) = 10
这种累加器方法的实现过程和思路前端
如果想要实现 fn()()
这种调用方式的函数,则在 fn
函数体内必定也会返回一个函数,以下:数组
function fn(){ return function(){ } }
再进一步,如果想要实现 fn()()()...
不定次数的调用,则意味着每一层的返回值均为一个函数,这就须要使用相似递归的方式去实现:app
function fn(){ var _fn = function(){ return _fn; } return _fn; }
若是每一层的返回值均为函数,那么如何返回累加结果呢?也就是说,在函数调用的最后一层,这个返回值应该是一个值而非函数。这里则须要使用 valueOf
这一方法,例子以下:框架
function fn1(){ } console.log(fn1); // function ... function fn2(){ } fn2.valueOf = function(){ return 1; } console.log(fn2); // function 1
注意,
fn2
在控制台的输出结果为function 1
,但其数值型隐式转换的结果为1
,也就是说console.log(+fn2)
或是var num = fn2 + 1
是能够正常地做为数值型进行计算的。而fn2()
的方式仍然能够将其做为函数运算。函数
于是,咱们只要给 _fn
添加 valueOf
方法就能够实如今不定次调用以后,获得一个「值」而非「函数」,以下:测试
function fn(){ var _fn = function(){ return _fn; } _fn.valueOf = function(){ return 12345; } return _fn; }
这里也可使用添加
toString()
的方式实现这一功能,与valueOf()
作法一致。prototype
这一步相对简单,使用内置对象 arguments
便可实现对函数中所传入的参数的获取,以下:code
function foo(){ console.log(arguments); } foo(1,2,3);
注意,这里的 arguments
不是数组,而是对象:对象
{ 0 : 1, 1 : 2, 2 : 3, callee : function foo(), length : 3, Symbol(Symbol.iterator) : function values(), __proto__ : Object, }
若要将其转换为数组,可使用以下方式:blog
var arr = [].slice.call(arguments); // 或是 var arr = Array.prototype.slice.call(arguments);
若一个对象含有
length
属性,则能够经过这种方式转换为数组形式
add(1)(2)(3)...
累加器是在最后一次调用后返回以前全部参数的累加和。也就是说咱们须要有一个地方能够保存先前的值或是计算结果。
在以前的代码框架下,显然不能将其保存在内层的 _fn
中。由于每层调用都至关于又一次的 _fn()
执行,在其中定义的变量会被覆盖。
使用全局变量固然是一种方式,可是这样会污染全局空间,不是最佳方案。
考虑到对 fn()()()...
的调用实际返回的是内层的 _fn
,意味着 fn
的局部变量其实也至关于 _fn
的全局变量。于是能够将保存先前参数的职责交给 fn
中的一个变量,代码以下:
function fn(){ var numList = []; var _fn = function(){ // 这里测试思路是否可行 numList.push(1); console.log(numList); return _fn; } _fn.valueOf = function(){ return 12345; } return _fn; } console.log(fn()()()); // [1, 1] // 注意这里虽然调用三次,但实际只执行了两次 push(1),第一次调用没有执行内层的 _fn(),而只是返回了它。
结合第三步,咱们能够经过 push()
或是 concat()
的方式将每一次的参数组合起来,以下:
function fn(){ var numList = [].slice.call(arguments); var _fn = function(){ // 注意这里的 arguments 是传入 _fn 的参数 var innerArguments = [].slice.call(arguments); numList = numList.concat(innerArguments); console.log(numList); return _fn; } _fn.valueOf = function(){ return 12345; } return _fn; } console.log(fn(1)(2)(3)); // [1, 2, 3]
固然,这里也可使用 push()
的方式,将每一次的参数推入数组。
这一步还有另外一种思路:用每一次的求和代替参数数组的合并。
既然已经获得了所有的参数集合,对其进行求和就比较简单了。最直接的方式固然是遍历数组并累加获得结果,也可使用数组的 reduce
方法实现,以下:
var arr = [1, 2, 3]; var sum = arr.reduce(function(num1, num2){ return num1 + num2; }); console.log(sum); // 6
结合第四步,替换 valueOf
中的返回值便可:
function fn(){ var numList = [].slice.call(arguments); var _fn = function(){ var innerArguments = [].slice.call(arguments); numList = numList.concat(innerArguments); return _fn; } _fn.valueOf = function(){ return numList.reduce(function(num1, num2){ return num1 + num2; }); } return _fn; } console.log(fn(1)(2)(3));
将其进行简化,获得最终结果:
function fn(){ var numList = [].slice.call(arguments); var _fn = function(){ numList = numList.concat([].slice.call(arguments)); return _fn; } _fn.valueOf = function(){ return numList.reduce(function(i, j){return i+j;}); } return _fn; }
固然,采用这种实现方式,对于形如
fn(1, 2, 3)(4)(5, 6, 7)
的调用方式也是没有问题的。