具有null或未定义值的JavaScript字符串连接行为

时间:2013-12-13 17:03:06

标签: javascript null undefined string-concatenation

您可能知道,在JavaScript '' + null = "null"'' + undefined = "undefined"中(在大多数浏览器中我都可以测试:Firefox,Chrome和IE)。我想知道这种奇怪的起源(Brendan Eich头脑中究竟是什么?!)以及是否有任何改变ECMA未来版本的目标。确实非常令人沮丧的是,'sthg' + (var || '')将字符串与变量连接起来并使用第三方框架,例如 Underscore 或其他框架,因为它使用锤子进行果冻钉敲击。< / p>

修改

为了满足StackOverflow所要求的标准并澄清我的问题,它有三个:

  • 使nullundefined转换为String串联中的字符串值的奇怪背后的历史是什么?
  • 在将来的ECMAScript版本中是否有可能改变这种行为?
  • 连接String与潜在nullundefined对象的最漂亮方式是什么,而不会遇到此问题(在"undefined"中获取"null" '' + (obj ? obj : '')中间的字符串)?根据主观标准 prettiest ,我的意思是:简短,干净,有效。没必要说{{1}}真的不漂亮......

6 个答案:

答案 0 :(得分:64)

您可以使用Array.prototype.join忽略undefinednull

['a', 'b', void 0, null, 6].join(''); // 'ab6'

根据spec

  

如果元素undefinednull,则让 next 为空字符串;   否则,让 next 成为ToString(元素)。


鉴于此,

      
  •     
    nullundefined转换为String连接中的字符串值的奇怪背后的历史是什么?
        

    事实上,在某些情况下,目前的行为是有道理的。

        
    function showSum(a,b) {
        alert(a + ' + ' + b + ' = ' + (+a + +b));
    }
        

    例如,如果上面的函数是在没有参数的情况下调用的,undefined + undefined = NaN可能比 + = NaN好。

        

    一般来说,我认为如果你想在字符串中插入一些变量,显示undefinednull是有意义的。也许,Eich也这么认为。

        

    当然,在某些情况下,忽略这些会更好,例如将字符串连接在一起时。但对于这些情况,您可以使用Array.prototype.join

      
  •   
  •     
    在将来的ECMAScript版本中是否有可能改变这种行为?
        

    很可能不是。

        

    由于已经存在Array.prototype.join,修改字符串连接的行为只会导致缺点,但没有优势。此外,它会破坏旧代码,因此它不会向后兼容。

      
  •   
  •     
    将String与潜在nullundefined连接起来的最漂亮的方法是什么?
        

    Array.prototype.join似乎是最简单的一个。它是否是最漂亮的可能是基于意见的。

      

答案 1 :(得分:27)

  

将String与潜在的null或未定义的对象连接起来的最漂亮的方法是什么,而不会陷入这个问题[...]?

有几种方法,你自己部分提到它们。简而言之,我能想到的唯一清洁方式是一个功能:

const Strings = {};
Strings.orEmpty = function( entity ) {
    return entity || "";
};

// usage
const message = "This is a " + Strings.orEmpty( test );

当然,您可以(并且应该)更改实际实施以满足您的需求。这就是为什么我认为这种方法更优越的原因:它引入了封装。

真的,你只需要问什么是最漂亮的&#34;方法是,如果你没有封装。你问自己这个问题,因为你已经知道你将进入一个你不能再改变实施的地方,所以你希望它立刻变得完美。但事情就是这样:需求,观点甚至环境都会发生变化。他们进化了。那么为什么不让自己改变实现只需要调整一行,也许只需要一两次测试?

你可以称之为作弊,因为它并没有真正回答如何实现实际的逻辑。但这就是我的观点:它并不重要。好吧,也许一点点。但实际上,没有必要担心,因为改变是多么简单。而且由于它没有内联,它看起来也更漂亮 - 无论你是以这种方式还是以更复杂的方式实现它。

如果在整个代码中,您不断重复||内联,则会遇到两个问题:

  • 您重复了代码。
  • 由于您复制了代码,因此将来很难维护和更改。

在高质量的软件开发方面,这些是众所周知的反模式。

有些人会说这太过于开销;他们会谈论表现。这是无意义的。首先,这几乎不会增加开销。如果这是你担心的,你选择了错误的语言。甚至jQuery也使用函数。人们需要克服微观优化。

另一件事是:你可以使用代码&#34;编译器&#34; = minifier。此区域中的好工具将尝试在编译步骤中检测要内联的语句。通过这种方式,您可以保持代码的清洁和可维护性,如果您仍然相信它或者确实有一个重要的环境,那么仍然可以获得最后一滴性能。

最后,对浏览器有一定的信心。他们会优化代码,现在他们做得非常好。

答案 2 :(得分:6)

ECMA规范

只是为了充实它在规范方面表现出来的原因,这种行为自version one以来一直存在。那里和5.1中的定义在语义上是等价的,我将展示5.1定义。

Section 11.6.1: The Addition operator ( + )

  

加法运算符执行字符串连接或数字   此外。

     

生产 AdditiveExpression AdditiveExpression +    MultiplicativeExpression 评估如下:

     
      
  1. lref 成为评估AdditiveExpression的结果。
  2.   
  3. lval 为GetValue( lref )。
  4.   
  5. rref 成为评估的结果   MultiplicativeExpression。
  6.   
  7. rval 为GetValue( rref )。
  8.   
  9. lprim 成为   ToPrimitive( lval中)。
  10.   
  11. rprim 为ToPrimitive( rval )。
  12.   
  13. 如果是Type( lprim )   字符串或类型( rprim )是String,然后是
      一个。返回的字符串是   连接ToString( lprim )后跟ToString( rprim
  14. 的结果   
  15. 返回应用加法运算的结果   ToNumber( lprim )和ToNumber( rprim )。参见下面的注释11.6.3。
  16.   

因此,如果任一值最终为String,则在两个参数(第7行)上使用ToString,并将它们连接起来(第7a行)。 ToPrimitive会保持所有非对象值不变,因此nullundefined不受影响:

Section 9.1 ToPrimitive

  

抽象操作ToPrimitive采用输入参数和可选参数 PreferredType 。抽象操作ToPrimitive将其输入参数转换为非Object类型...根据表10进行转换:

对于所有非Object类型,包括NullUndefined[t]he result equals the input argument (no conversion).所以ToPrimitive此处不执行任何操作。

最后,Section 9.8 ToString

  

抽象操作ToString根据表13将其参数转换为String类型的值:

表13给出了"undefined"类型的Undefined"null"类型的Null

会改变吗?它甚至是一个奇怪的&#34;?

正如其他人所指出的那样,这种情况不太可能发生变化,因为它会破坏向后兼容性(并没有带来真正的好处),甚至更多,因为这种行为与规范的1997版本相同。我也不会认为这很奇怪。

如果您要更改此行为,是否会更改ToStringnull的{​​{1}}的定义,或者您是否特殊情况下这些值的加法运算符? undefined在整个规范中使用了很多很多地方 ToString似乎是代表"null"的无可争议的选择。举几个例子,在Java null中是字符串"" + null,在Python "null"中是字符串str(None)

解决方法

其他人提供了很好的解决方法,但我想补充一点,我怀疑你是否想要使用"None"作为你的策略,因为它将entity || ""解析为true"true"为{ {1}}。 this answer中的数组联接具有更多预期行为,或者您可以更改this answer的实现以检查false""entity == null都为真)

答案 3 :(得分:2)

  

在将来的ECMAScript版本中是否有可能改变这种行为?

我会说机会非常渺茫。有几个原因:

我们已经知道ES5和ES6是什么样的

未来的ES版本已经完成或正在草拟。没有人,afaik,改变这种行为。这里要记住的是,在浏览器中建立这些标准需要花费数年时间,因为您可以使用这些标准编写应用程序,而无需依赖将其编译为实际Javascript的代理工具。

试着估计持续时间。大多数浏览器都没有完全支持ES5,可能还需要几年时间。 ES6尚未完全指定。出乎意料的是,我们至少还要看五年。

浏览器做自己的事情

众所周知,浏览器会针对某些主题做出自己的决定。您不知道所有浏览器是否完全支持此功能的方式完全相同。当然,一旦它成为标准的一部分你就会知道,但截至目前,即使它被宣布成为ES7的一部分,它也只是充其量的猜测。

浏览器可能会在这里做出自己的决定,特别是因为:

此更改正在破坏

标准最重要的一点是,他们通常尝试向后兼容。对于必须在各种环境中运行相同代码的Web,尤其为true。

如果标准引入了新功能,并且在旧浏览器中不支持,那就是一回事。告诉您的客户更新他们的浏览器以使用该网站。但是,如果你更新你的浏览器,并突然有一半的互联网休息时间,这是一个错误嗯 - 没有。

当然,这种特殊的改变不太可能打破很多脚本。但这通常是一个不好的论点,因为标准是普遍的,必须考虑到每一个机会。考虑一下

"use strict";

作为切换到严格模式的指令。它表明了标准在尝试使所有内容兼容方面付出的巨大努力,因为它们可以将严格模式设置为默认模式(甚至只有模式)。但是通过这个聪明的指令,您可以允许旧代码在没有更改的情况下运行,并且仍然可以利用新的更严格的模式。

向后兼容性的另一个示例:===运算符。 ==从根本上是有缺陷的(虽然有些人不同意),它可能只是改变了它的含义。相反,引入了===,允许旧代码仍然运行而不会中断;同时允许使用更严格的检查来编写新程序。

对于破坏兼容性的标准,必须有一个很好的理由。这将我们带到

没有充分的理由

是的,它会让你烦恼。这是可以理解的。但最终,非常容易无法解决。使用||,写一个函数 - 无论如何。你几乎可以免费使用它。那么真正什么是投资所有时间和精力分析这个我们知道正在破坏的变化的好处呢?我只是不明白这一点。

Javascript在设计上有几个缺点。随着语言变得越来越重要和强大,它越来越成为一个更大的问题。虽然有很好的理由改变它的很多设计,其他事情并不意味着要改变


免责声明:此答案部分基于意见。

答案 4 :(得分:1)

要添加null和'',它们需要满足最小公共类型标准,在这种情况下是一个字符串类型。

null由于这个原因被转换为“null”,因为它们是字符串,所以它们是串联的。

数字也是如此:

4 +''='4'

因为其中有一个字符串无法转换为任何数字,所以4将转换为字符串。

答案 5 :(得分:0)

为什么不过滤以检查真实性然后加入?

const concatObject = (obj, separator) =>
    Object.values(obj)
        .filter((val) => val)
        .join(separator);


let myAddress = {
    street1: '123 Happy St',
    street2: undefined,
    city: null,
    state: 'DC',
    zip: '20003'
}

concatObject(myAddress, ', ')
> "123 Happy St, DC, 20003"