Number()和new number()的区别以及造个简单的轮子

JavaScript

February 03, 2018 · 14 mins read

看JavaScript文档的时候注意到了这种用法var n1 = Number(123);,冒出的第一个疑问就是和var n2 = new Number(123);有什么区别呢?

首先用typeof做下探测,n1是number而n2是object,他们的本质区别就是type不同。

有趣的是Number内部肯定知道是怎么调用的它,假设在没有Number的情况下,如果我要实现个类似的类应该怎么做呢?

最先想到的就是根据caller来区分,但在实验的过程中发现两个问题:

  1. 全局调用的时候没有caller
  2. 就算知道caller也无法区分它是function调用还是构造对象

所以caller这条路就走不通了,既然需要在运行期区分,那么该真爱this登场了。this指向当前构造的对象,那就可以区分是function调用还是构造对象了。

新轮子命名为WeiWeiNumber,思路理清楚后就剩施工了。为了更接近Number的行为,在开工前先用测试数据探测下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
console.log(Number(123));      //123
console.log(Number(+123));     //123
console.log(Number(-123));     //-123
console.log(Number("123"));    //123
console.log(Number("+123"));   //123
console.log(Number("-123"));   //-123
console.log(Number("abc123")); //NaN
console.log(Number(NaN));      //NaN

console.log(new Number(123)); //save as above except type
console.log(new Number(+123));
console.log(new Number(-123));
console.log(new Number("123"));
console.log(new Number("+123"));
console.log(new Number("-123"));
console.log(new Number("abc123"));
console.log(new Number(NaN));

在测试过程发现123 == new Number('123')是返回true的,但我们的123 == new WeiWeiNumber('123')却返回false,难道浏览器不给WeiWeiNumber国民待遇?

首先分析浏览器是不可能把123 auto-box成Number对象的,因为两个对象==肯定是false的,所以一定是把Number对象auto-unbox成原始type(值type)。 查了一下文档对象刚好有个valueOf()方法用来返回这个对象代表的原始值。(后来测试过程中发现valueOf()或toString()实现任一一个方法都能让浏览器返回true)

string快速转换成number的方法是"123" * 1 = 123,但这是语法糖,实际上调用的是Number("123") * 1,我们预设Number类是不存在的所以选择计算ASCII码的差值。

下面是实现WeiWeiNumber的源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
function WeiWeiNumber(i){
    var primitiveValue = 0;
    
    if(typeof i === "number"){
            primitiveValue = i;
    }else{
        //正则表达式抓取正负符号和数字的文本值
        var regR = /^([\+\-]?)([0-9]+)$/.exec(i);
        if(regR !== null){
            //数字的文本值,相当于Java的group(2)
            var nstr = regR[2];
            var nstrlen = nstr.length;
            
            //callee就是本function,避免hardcode类名
            var nResult = arguments.callee(0);
            for(idx in nstr){
                //通过计算ASCII码的差值转换成数字
                nResult += (nstr[idx].charCodeAt(0) - "0".charCodeAt(0)) 
                              * Math.pow(10, nstrlen - idx -1);
            }
            
            //判断正负值
            if(regR[1] === "-"){
                primitiveValue = -nResult;
            }else{
                primitiveValue = nResult;
            }
        }else{
            primitiveValue = NaN;
        }
    }
    
    if(this instanceof arguments.callee){
        //构造对象
        this.valueOf = function(){
            //为了==判断返回true
            return primitiveValue;
        }
        
        this.toString = function(){
            //为了==判断返回true
            return primitiveValue + '';
        }
        return this;
    }else{
        //invoke as function
        return primitiveValue;
    }
}

上次更新: 2020-02-06