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

JavaScript String concatenation behavior with null or undefined values

本文关键字:字符串 JavaScript null 未定义 具有      更新时间:2023-09-26

正如您所知,在JavaScript '' + null = "null"'' + undefined = "undefined"中(在我可以测试的大多数浏览器中:Firefox、Chrome和IE)。我想知道这种奇怪现象的起源(Brendan Eich的脑子里到底是什么?!),以及在未来的ECMA版本中是否有改变它的目标。必须使用'sthg' + (var || '')将字符串与变量连接起来,并使用Undercore等第三方框架,或者使用锤子敲击果冻钉,这确实非常令人沮丧。

编辑:

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

  • JS在String级联中将nullundefined转换为其字符串值的奇怪现象背后的历史是什么
  • 在未来的ECMAScript版本中,这种行为是否有可能发生变化
  • 在不陷入这个问题的情况下,将String与潜在的nullundefined对象连接起来的最漂亮的方法是什么(在字符串的中间获得"null"的一些"undefined")?根据主观标准最漂亮,我的意思是:短、干净、有效。不用说'' + (obj ? obj : '')真的不漂亮

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

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

根据规格:

如果元素undefinednull,则让下一个为空字符串;否则,让下一个为ToString(元素)。


鉴于此,

  • JS在String级联中将nullundefined转换为其字符串值的奇怪现象背后的历史是什么

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

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

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

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

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

  • 在未来的ECMAScript版本中,这种行为有可能改变吗

    很可能不会。

    由于已经存在Array.prototype.join,因此修改字符串串联的行为只会带来缺点,而不会带来好处。此外,它会破坏旧代码,因此不会向后兼容。

  • 将字符串与潜在的nullundefined连接起来最漂亮的方法是什么

    CCD_ 29似乎是最简单的一个。它是否最漂亮可能取决于人们的看法。

在不陷入这个问题的情况下,将字符串与潜在的null或未定义对象连接的最漂亮的方法是什么[…]?

有几种方法,你自己也有部分提到过。简而言之,我能想到的只有干净方法是一个函数:

const Strings = {};
Strings.orEmpty = function( entity ) {
    return entity || "";
};
// usage
const message = "This is a " + Strings.orEmpty( test );

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

实际上,如果你没有封装,你只需要问"最漂亮"的方式是什么。你问自己这个问题是因为你已经知道你将陷入一个无法再改变实现的境地,所以你希望它立即变得完美。但事实就是这样:需求、视图甚至环境都会发生变化。它们在进化。那么,为什么不允许自己只需调整一行代码,或者一两个测试就可以更改实现呢?

你可以称之为欺骗,因为它并没有真正回答如何实现实际的逻辑。但这就是我的观点:这并不重要。嗯,也许有一点点。但实际上,没有必要担心,因为改变是多么简单。由于它没有内联,所以它看起来也漂亮得多——无论您是否以这种方式或更复杂的方式实现它。

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

  • 您重复了代码
  • 而且,由于重复代码,将来很难进行维护和更改

当涉及到高质量的软件开发时,这两点通常被称为反模式。

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

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

最后,对浏览器有一些信心。他们将优化代码,而且这些天他们在这方面做得非常好。

ECMA规范

为了进一步说明它在规范方面表现出这种行为的原因,这种行为从版本一开始就存在了。那里的定义和5.1中的定义在语义上是等价的,我将展示5.1的定义。

第11.6.1节:加法运算符(+)

加法运算符执行字符串串联或数字附加

生产AdditiveExpressionAdditiveExpression+ 乘法表达式的计算如下:

  1. lref是计算AdditiveExpression的结果
  2. lval为GetValue(lref
  3. rref是评估的结果乘法表达式
  4. rval为GetValue(rref
  5. lprim为ToPrimitive(lval
  6. rprim为ToPrimitive(rval
  7. 如果类型(lprim)为字符串或类型(rprim)为字符串,则
    a.返回作为连接ToString(lprim)和ToString(>em>rprim)的结果
  8. 将加法运算的结果返回到ToNumber(lprim)和ToNumber(rprim)。请参见下面11.6.3的注释

因此,如果其中一个值最终是String,则在两个参数上都使用ToString(第7行),并且这些参数被连接(第7a行)。ToPrimitive返回所有未更改的非对象值,因此nullundefined不受影响:

第9.1节至原始

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

对于所有非Object类型,包括NullUndefined[t]he result equals the input argument (no conversion).,所以ToPrimitive在这里什么都不做。

最后,第9.8节ToString

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

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

它会改变吗?这甚至是一个"怪事"吗

正如其他人所指出的,这种情况不太可能改变,因为它会破坏向后兼容性(并且不会带来真正的好处),更重要的是,考虑到自1997年版本的规范以来,这种行为是相同的。我也不会真的认为这是一个奇怪的现象。

如果要更改此行为,您会更改nullundefinedToString定义吗?还是会对这些值的加法运算符进行特殊处理?ToString在整个规范和CCD_ 49似乎是代表CCD_ 50的一个没有争议的选择。举几个例子,在Java中"" + null是字符串"null",在Python中str(None)是字符串"None"

变通办法

其他人给出了很好的解决方案,但我想补充一点,我怀疑你是否想使用entity || ""作为你的策略,因为它将true解析为"true",但将false解析为""。此答案中的数组联接具有更期望的行为,或者您可以更改此答案的实现以检查entity == nullnull == nullundefined == null都为true)。

使用合并-语法为??

例如,可以在浏览器控制台中测试:

  • "Hello " + (null ?? "")
  • "Hello " + (undefined ?? "")

两者都会产生:'Hello '

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

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"

在未来的ECMAScript版本中,这种行为是否有可能发生变化?

我想说机会很渺茫。原因有几个:

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

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

试着估计一下持续时间。即使是ES5也没有得到大多数浏览器的完全支持,这可能还需要几年的时间。ES6甚至还没有完全指定。出乎意料的是,我们期待着至少再过五年。

浏览器做自己的事情

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

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

这一变化正在打破

标准最重要的一点是它们通常试图向后兼容。这是,尤其是对于必须在各种环境上运行相同代码的web来说更是如此。

如果标准引入了一个新功能,而旧浏览器不支持它,那是一回事。告诉您的客户端更新浏览器以使用该网站。但如果你更新了浏览器,突然有一半的互联网对你来说中断了,那就是一个错误。

当然,这个特殊的更改不太可能破坏很多脚本。但这通常是一个糟糕的论点,因为一个标准是普遍的,必须考虑到每一个机会。只要考虑

"use strict";

作为切换到严格模式的指令。这表明,一个标准在尝试使所有内容都兼容方面付出了很大的努力,因为他们本可以将严格模式作为默认模式(甚至是唯一模式)。但有了这个聪明的指令,您可以让旧代码在不做任何更改的情况下运行,并且仍然可以利用新的、更严格的模式。

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

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

没有什么好的理由

是的,它让你很烦恼。这是可以理解的。但最终,没有什么是不能轻易解决的。使用||,编写一个函数——不管怎样。你几乎可以免费让它发挥作用。那么,投入所有时间和精力来分析这一我们知道无论如何都在打破的变化,真正的好处是什么呢?我就是不明白这有什么意义。

Javascript在设计中有几个弱点。随着语言变得越来越重要和强大,它也越来越成为一个更大的问题。但是,尽管有充分的理由改变它的许多设计,其他东西并不意味着要改变


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

要添加null和",它们需要满足最低通用类型标准,在本例中为字符串类型。

由于这个原因,null被转换为"null",因为它们是字符串,所以两者被连接在一起。

数字也是如此:

4+''='4'

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