parseInt和一元加号,什么时候使用哪个?

208

这条线有哪些区别:

var a = parseInt("1", 10); // a === 1

和这一行

var a = +"1"; // a === 1

这个jsperf测试显示在当前版本的Chrome中,一元运算符比parseInt更快,假设它是为node.js而设计的!?

如果我尝试转换不是数字的字符串,两者都会返回NaN

var b = parseInt("test", 10); // b === NaN
var b = +"test"; // b === NaN

那么,在什么情况下我应该优先使用parseInt而不是一元加法(特别是在node.js中)?

编辑:与双波浪线运算符~~有什么区别?


3
基准测试 http://jsperf.com/parseint-vs-unary-operator - Roko C. Buljan
@RokoC.Buljan 这个服务好像已经停止了。有任何更新吗? - AgainPsychoX
6个回答

399

终极的任何转数字表格: 转换表

EXPRS = [
    'parseInt(x)',
    'parseFloat(x)',
    'Number(x)',
    '+x',
    '~~x',
    'x>>>0',
    'isNaN(x)'

];

VALUES = [
    '"123"',
    '"+123"',
    '"-123"',
    '"123.45"',
    '"-123.45"',
    '"12e5"',
    '"12e-5"',
    
    '"0123"',
    '"0000123"',
    '"0b111"',
    '"0o10"',
    '"0xBABE"',
    
    '"4294967295"',
    '"123456789012345678"',
    '"12e999"',

    '""',
    '"123foo"',
    '"123.45foo"',
    '"  123   "',
    '"foo"',
    '"12e"',
    '"0b567"',
    '"0o999"',
    '"0xFUZZ"',

    '"+0"',
    '"-0"',
    '"Infinity"',
    '"+Infinity"',
    '"-Infinity"',
    'BigInt(1)',

    'null',
    'undefined',
    'true',
    'false',
    'Infinity',
    'NaN',

    '{}',
    '{valueOf: function(){return 42}}',
    '{toString: function(){return "56"}}',

];

//////

function wrap(tag, s) {
    if (s && s.join)
        s = s.join('');
    return '<' + tag + '>' + String(s) + '</' + tag + '>';
}

function table(head, rows) {
    return wrap('table', [
        wrap('thead', tr(head)),
        wrap('tbody', rows.map(tr))
    ]);
}

function tr(row) {
    return wrap('tr', row.map(function (s) {
        return wrap('td', s)
    }));
}

function val(n) {
    return n === true || Number.isNaN(n) || n === "Error" ? wrap('b', n) : String(n);
}

var rows = VALUES.map(function (v) {
    var x = eval('(' + v + ')');
    return [v].concat(EXPRS.map(function (e) {
        try {
            return val(eval(e));
        } catch {
            return val("Error");
        }
    }));
});

document.body.innerHTML = table(["x"].concat(EXPRS), rows);
table { border-collapse: collapse }
tr:nth-child(odd) { background: #fafafa }
td { border: 1px solid #e0e0e0; padding: 5px; font: 12px monospace }
td:not(:first-child) { text-align: right }
thead td { background: #3663AE; color: white }
b { color: red }


4
请在这张表格中添加 "NaN"。 - chharvey
2
在这个表格中添加一个 isNaN 列可能是值得的:例如,isNaN("") 是 false(即被认为是一个数字),但 parseFloat("")NaN,如果你试图在将其传递给 parseFloat 之前使用 isNaN 来验证输入,这可能会导致问题。 - Retsam
2
你还应该将'{valueOf: function(){return 42}, toString: function(){return "56"}}'添加到列表中。混合结果很有趣。 - murrayju
11
表格的总结是 + 只是写作更短的方式来表示“数字”,而其他复杂的方式只是会在一些边缘情况下出现错误的方式吗? - Mihail Malostanidis
[].undef是一种东西吗,还是这只是生成未定义的任意方式?通过谷歌找不到与JS相关的"undef"记录。 - jcairney
显示剩余3条评论

215

好的,这里是我所知道的一些区别:

  • 空字符串""被计算为0,而parseInt将其计算为NaN。在我看来,一个空字符串应该是NaN

  +'' === 0;              //true
  isNaN(parseInt('',10)); //true
  • 一元运算符+的行为更像parseFloat,因为它也接受小数。

    另一方面,parseInt会在遇到非数字字符时停止解析,比如表示十进制小数点的符号.

  •   +'2.3' === 2.3;           //true
      parseInt('2.3',10) === 2; //true
    
  • parseIntparseFloat 从左到右解析并构建字符串。如果它们遇到无效字符,则返回已解析的(如果有)数字,如果没有解析为数字则返回NaN

    另一方面,一元的+符号如果整个字符串无法转换为数字,则返回NaN

  •   parseInt('2a',10) === 2; //true
      parseFloat('2a') === 2;  //true
      isNaN(+'2a');            //true
    
  • @Alex K.的评论所示,parseIntparseFloat将按字符解析。这意味着十六进制和指数符号将失败,因为xe被视为非数字组件(至少在基于十进制的情况下)。

    但是一元运算符+可以正确地转换它们。

  •   parseInt('2e3',10) === 2;  //true. This is supposed to be 2000
      +'2e3' === 2000;           //true. This one's correct.
    
      parseInt("0xf", 10) === 0; //true. This is supposed to be 15
      +'0xf' === 15;             //true. This one's correct.
    

    6
    当使用基数时,+"0xf" != parseInt("0xf", 10) - Alex K.
    我迄今为止最喜欢你的答案,你能否也解释一下双波浪线运算符~~的区别是什么? - hereandnow78
    @hereandnow78 这个可以在这里找到解释。它是Math.floor()的按位等效,基本上是去掉小数部分。 - Joseph
    如果您计划使用不同的进制,那么 parseInt("0xf") 也会得到15。 - Bergi
    6
    实际上,"2e3"不是表示整数2000的有效方式。但它可以作为一个浮点数被正确地解析:parseFloat("2e3")会返回2000这个答案。而"0xf"则需要至少使用十六进制,这就是为什么parseInt("0xf", 10)返回0,而parseInt("0xf", 16)返回你期望的值15的原因。 - Bart
    2
    @Joseph the Dreamer 和 @hereandnow78:双波浪号截去数字的小数部分,而 Math.floor 返回最接近的较低数字。它们对于正数的工作方式相同,但是Math.floor(-3.5) == -4~~-3.5 == -3 - Albin

    12

    我认为thg435的回答中的表格是全面的,但我们可以用以下模式进行总结:

    • 一元加号不会将所有falsy值视为相同,但它们都变成了falsy。
    • 一元加号将true转换为1,但将"true"转换为NaN
    • 另一方面,parseInt对于不是纯数字的字符串更加宽松。parseInt('123abc') === 123,而+则报告NaN
    • Number将接受有效的十进制数,而parseInt仅删除小数点后的内容。因此,parseInt模仿了C的行为,但可能并不适合评估用户输入。
    • 两者都修剪字符串中的空白。
    • parseInt是一个设计不良的解析器,接受八进制和十六进制输入。一元加号只接受十六进制。

    falsy值按照在C中有意义的方式转换为Numbernullfalse都是零。""变成0并没有完全遵循这个约定,但对我来说有足够的意义。

    因此,我认为如果您要验证用户输入,则一元加号在除了接受小数之外的所有内容中都具有正确的行为(但在我的实际情况中,我更关心捕获电子邮件输入,而不是userId、完全省略值等),而parseInt则过于宽松。


    4
    “Unary plus only takes hexadecimal” 你的意思不是十进制吗? (注:该翻译尽可能准确地传达了原文的意思,并且没有添加任何额外的信息或解释。) - krillgar

    1

    注意,Node.JS中parseInt比+一元运算符更快, +或|0更快的说法是错误的,只有对于NaN元素才更快。

    看看这个例子:

    var arg=process.argv[2];
    
    rpt=20000;
    mrc=1000;
    
    a=[];
    b=1024*1024*1024*1024;
    for (var i=0;i<rpt;i++)
     a[i]=Math.floor(Math.random()*b)+' ';
    
    t0=Date.now();
    if ((arg==1)||(arg===undefined))
     for (var j=0;j<mrc;j++) for (var i=0;i<rpt;i++) {
      c=a[i]-0;
     }
    t1=Date.now();
    if ((arg==2)||(arg===undefined)) {
     for (var j=0;j<mrc;j++) for (var i=0;i<rpt;i++) {
      d=a[i]|0;
     }
    }
    t2=Date.now();
    if ((arg==3)||(arg===undefined)) {
     for (var j=0;j<mrc;j++) for (var i=0;i<rpt;i++) {
      e=parseInt(a[i]);
     }
    }
    t3=Date.now();
     if ((arg==3)||(arg===undefined)) {
     for (var j=0;j<mrc;j++) for (var i=0;i<rpt;i++) {
      f=+a[i];
     }
    }
    t4=Date.now();
    
    console.log(a[i-1],c,d,e,f);
    console.log('Eseguiti: '+rpt*mrc+' cicli');
    console.log('parseInt '+(t3-t2));
    console.log('|0 '+(t2-t1));
    console.log('-0 '+(t1-t0));
    console.log('+ '+(t4-t3));
    

    1
    我建议使用Math.floor(如果您知道数字为正数,则可以使用~~)而不是parseString。因为+(expression)更像parseFloat,所以它超出了范围。看一下这个小基准测试:
    // 1000000 iterations each one
    node test_speed
    Testing ~~, time: 5 ms
    Testing parseInt with number, time: 25 ms
    Testing parseInt with string, time: 386 ms
    Testing Math.floor, time: 18 ms
    

    基准测试的源代码:

    
    /* el propósito de este script es evaluar
    que expresiones se ejecutan más rápido para así 
    decidir cuál usar */
    
    main()
    async function main(){
        let time, x 
        let number = 23456.23457
        
        let test1 = ()=>{
            x = 0
            time = Date.now() 
            for(let i=0;i<1000000;i++){
                let op = Math.floor(number / 3600)
                x = op
            }
            console.info("Testing Math.floor, time:", Date.now() - time, "ms")
        }
    
        let test2 = ()=>{
            x = 0
            time = Date.now() 
            for(let i=0;i<1000000;i++){
                let op = parseInt(number / 3600)
                x = op
            }
            console.info("Testing parseInt with number, time:", Date.now() - time, "ms")
        }
    
        let test3 = ()=>{
            x = 0
            time = Date.now() 
            for(let i=0;i<1000000;i++){
                let op = parseInt((number / 3600).toString())
                x = op
            }
            console.info("Testing parseInt with string, time:", Date.now() - time, "ms")
        }
    
        let test4 = ()=>{
            x = 0
            time = Date.now() 
            for(let i=0;i<1000000;i++){
                let op = ~~(number / 3600)
                x = op
            }
            console.info("Testing ~~, time:", Date.now() - time, "ms")
        }
        
        test4()
        test2()
        test3()
        test1()
        
    }
    

    -2

    也要考虑性能。我很惊讶parseInt在iOS上比一元加号更快 :) 这对于有重度CPU消耗的Web应用程序非常有帮助。作为一个经验法则,我建议JS优化人员从移动性能的角度考虑任何JS运算符而不是另一个。

    所以,要先考虑移动端 ;)


    正如其他帖子所解释的那样,它们执行非常不同的功能,因此你不能轻易地将一个替换为另一个... - Bergi
    @Bergi,没错,但它们也有很多共同点。告诉我一个在JavaScript中绝对是唯一正确选择的性能解决方案?通常这就是为什么我们需要经验法则。其余的取决于具体任务。 - Arman
    6
    @ArmanMcHitaryan 这是无用的微观优化,不值得。看看这篇文章 - http://fabien.potencier.org/article/8/print-vs-echo-which-one-is-faster - webvitaly
    @webvitaly,不错的文章。总有一些非常注重性能的人喜欢编写“最快可能”的代码,在某些特定的项目中这并不坏。这就是为什么我提到了“JS优化大师们需要考虑”。当然这不是必须的:),但我个人认为这样更易读。 - Arman
    你有这个的引用吗?你的链接已经失效了。 - djechlin
    我可以告诉你们,像这样的微小优化使我的3D生物医学成像代码以可接受的性能运行,所以我不会轻易地忽略这些东西。我还看到将Math.trunc更改为~~使程序生成的画布渲染运行得相当流畅,所以你的情况可能有所不同。这些优化之所以起作用,是因为它们在一个紧密的循环中被调用,每帧调用数百万次。 - SapphireSun

    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接