这里说的循环调用不是指函数的递归,而是指函数的返回值仍然是函数,可以继续传参调用,如下面的代码:

function add(n){
    ...
}

add(1);
add(1)(2)(3);



这个循坏调用问题的来源是笔者在codewars里遇到的一道题,题目信息如下图:

FUNCTION cursor 重复利用 js重复调用函数_javascript


翻译一下题目大意:实现add函数,函数支持累加功能,并在累加完成后返回累加结果。


笔者开始考虑的是用callee来实现,代码如下:

var result = 0;
function add(n){
    result += n;
    return arguments.callee;
}



对这个实现简单说明一下:

arguments是进行函数调用时,除了指定的参数外,还另外创建的一个隐藏对象(例如上面调用函数add时,除了显式的参数n外还会额外创建一个隐藏参数对象arguments,且这个隐藏参数对象arguments只能在调用的函数add内部使用),这个隐藏对象代表正在执行的函数和调用它的函数的参数。arguments.callee可以返回正在执行的函数本身,而arguments[i]则可以返回参数列表中第i个参数。


当add函数循环调用时,前一次调用完成后返回的是add函数本身,然后把后一次的参数传入,开始后一次调用。下面用几个测试用例验证一下结果是否正确:

var result = 0;
function add(n){
  result += n;
  return arguments.callee;
}

add(1)(2);
console.log(result);

result = 0;
add(3)(4);
console.log(result);



验证结果如下:

FUNCTION cursor 重复利用 js重复调用函数_函数_02


累加功能验证没有问题,如果我们是实现一个业务功能,那么这么写也没毛病,只是每次循环调用完成后都要去result里取值。不过codewars的用例是肯定跑不过的,因为每次函数调用返回的都是一个函数,而不是一个值(如add(1)(2)(3)的返回结果期望是6,但上面的实现返回的则是一个add函数对象)。


再考虑一下,估计很多同学都会有下面这个哥们的问题:

FUNCTION cursor 重复利用 js重复调用函数_javascript_03


翻译一下这位暴躁老哥的回复重点:他无法理解同一个函数的返回结果既是一个值又是一个对象——函数add的返回怎么可以既是一个值,又可以作为一个函数传入参数执行呢?换句话说,就是最后一次循环调用需要返回一个值,除最后一次循环调用外其他调用需要返回一个可以传入参数的函数。


一个函数的返回结果当然不可能既是一个值又是一个对象,所以我们需要从另一个角度来考虑这个问题:当函数本身需要作为一个值加入运算时(在题目中就是作为一个数值),能否自动把函数转换为期望的数据类型呢?


当然是可以的,就是重写add函数返回对象的valueof方法。


下面先对valueof方法作一个简单说明:

无论是一元加号(用于数值运算)还是二元加号(用于字符串拼接),都会尝试将对象转为期望的数据类型,无论是一元加号还是二元加号,首先会调用对象的valueof方法(valueof是从object继承过来,默认返回对象本身),如果valueof方法的返回结果是基本数据类型,则会用这个值,如果是对象则会继续调用对象的toString方法,如果toString方法的返回结果是基本数据类型,则会用这个值,否则报错。


重写valueof方法的实现代码如下:

function add(n){
  var fn = function(x) {
    return add(n + x);
  };
  
  fn.valueOf = function() {
    return n;
  };
  
  return fn;
}



继续用几个测试用例验证一下结果是否正确:

function add(n){
  var fn = function(x) {
    return add(n + x);
  };
  
  fn.valueOf = function() {
    return n;
  };
  
  return fn;
}
console.log(add(1)(3));
console.log(add(4)(5));



验证结果如下:

FUNCTION cursor 重复利用 js重复调用函数_valueof_04


累加功能验证通过。简单地说,add(1)(3)的返回结果实际上是在函数add内定义的fn对象,当我们用console.log打印返回结果或者把返回结果用于一元运算时,会隐式调用fn对象的valueof方法进行转换。