2014年4月

故事的起因源于群中有人提的一个问题:

a(1)=1;

a(1)(2)=3;

a(1)(2)(3)=6;

还原出函数a

好吧,我刚看到这个题目的时候,第一反应就是递归的闭包,然后就写了下面这一版:

var a = function(num){
    this.sum = this.sum || 0;
    if(num){
        this.sum += num;
        return a;
    }
    var result = this.sum;
    delete this.sum;
    return result;
}

但是这样的执行是这样的:

a(1)();    // 1

a(1)(2)(); // 3

和题目要求的不一样,而且还有sum没有及时重置,所以,这个办法不行

然后想了一会儿没想出好办法,就去求助原公司大牛。果然大牛就是不一样,给出了下面这个解决方案

function a(x){
    var list = [];
    list.push(x);
    var y = function(xx){
        list.push(xx);
        return y;
    };
    y.valueOf = function(){
        return list.reduce(function(a,b){return a+b;},0);
    }
    return y;
}

利用valueOf和闭包里的一个list做的,果断膜拜了一下

然后,后来有看到群里有人给出了这样的答案:

function a(num){
    function b(i){
        return a(num+i);
    }
    b.toString = function(){
        return num;
    };
    return b;
}

也是可以的,然后就比较好奇,这个valueOf和toString是怎样被执行的呢?

果断去补习了一下基础知识:

对于所有的js对象(除了null以外),都有valueOf和toString方法,这是为了更方便的对一个对象进行值的操作

比如说我们要比较两个object的大小的时候,就可以定义一下它的valueOf方法或者toString來比较大小,那么到底什么时候会调用这两个方法呢?

来看一个小测试:

    var bbb = {
        i: 10,
        toString: function() {
          console.log('toString');
          return this.i;
        },
        valueOf: function() {
          console.log('valueOf');
          return this.i;
        }
      }
      alert(bbb);          // 10 toString
      alert(+bbb);         // 10 valueOf
      alert(''+bbb);       // 10 valueOf
      alert(String(bbb));  // 10 toString
      alert(Number(bbb));  // 10  valueOf
      alert(bbb == '10');  // true valueOf
      alert(bbb === '10'); // false

貌似是跟字符串有关的会调用toString,而根数字有关的会调用valueOf方法,但是,我们发现(''+bbb)这个调用的是valueOf,而最后的===则什么都没有调用,那我们再试一下:

    var aa = {
        i: 10,
        toString: function() {
          console.log('toString');
          return this.i;
        }
      }
      alert(aa);         // 10 toString
      alert(+aa);        // 10 toString
      alert(''+aa);      // 10 toString
      alert(String(aa)); // 10 toString
      alert(Number(aa)); // 10 toString
      alert(aa == '10'); // true toString

很和谐的全是toString,再看看valueOf的:

    var bb = {
        i: 10,
        valueOf: function() {
          console.log('valueOf');
          return this.i;
        }
      }
      alert(bb);         // [object Object]
      alert(+bb);        // 10 valueOf
      alert(''+bb);      // 10 valueOf
      alert(String(bb)); // [object Object]
      alert(Number(bb)); // 10 valueOf
      alert(bb == '10'); // true valueOf

我们发现中间出现了[object Object],貌似是从Object那里继承过来的,我们把它去掉试试:

Object.prototype.toString = null;
var cc = {
    i: 10,
    valueOf: function() {
        console.log('valueOf');
        return this.i;
    }
}
alert(cc);         // 10 valueOf
alert(+cc);        // 10 valueOf
alert(''+cc);      // 10 valueOf
alert(String(cc)); // 10 valueOf
alert(Number(cc)); // 10 valueOf
alert(cc == '10'); // true valueOf

貌似和谐多了,那我们基本可以看到是这样的,如果只定义了toString方法,那么当遇到需要类型转换的时候,就会直接调用toString方法,然后如果只定义了valueOf方法的话,转换数字的时候会优先调用valueOf方法,然后在转换字符串的时候,如果实在没有toString可以调用才会用valueOf代替,对于有操作符的情况下(比如=),valueOf的优先级要比toString要高

感谢 小秦 同学纠正:当转换为数字类型的时候,如果 valueOf 返回的不是基础类型,才会调用 toString 方法

好了,那我们还有最后一个问题,(''+bbb),这个字符串拼接为什么是调用的valueOf呢?

这个问题应该是因为+操作符上,这里会有一个getValue操作,然后就调用valueOf方法了

嗯,好的,今天就这么多,有不对的地方欢迎指正~