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


69

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

编辑:

为了满足StackOverflow所要求的标准并阐明我的问题,这是一个三方面的问题:

  • 导致JS转换null或串联时转换undefined为字符串值的奇数背后的历史是什么String
  • 在将来的ECMAScript版本中是否有可能对此行为进行更改?
  • 什么是连接最漂亮的方式String与潜在nullundefined对象不落入这个问题(得到一些"undefined""null"在字符串的中间)?用最漂亮的主观标准,我的意思是:简短,干净和有效。不必说那'' + (obj ? obj : '')不是很漂亮……

+1同意,我期待的是相同的行为(现在,如果if条件减慢了我的应用程序的数据运行速度,这会花费我很多钱)。我有R背景,您会明白您的意思paste0("a", NULL) == "a"
米歇尔2014年

实际上,行为正是我所期望的。将null和undefined转换为与空字符串不同的字符串表示形式,但是编写一个看其arg并返回空字符串(如果它为null或undefined)以及其他任何toString的函数将很容易。
Mike Lippert 2014年

@MikeLipper很容易,但不是本机的,因此没有合并(希望在JS中没有运算符重载)。对于紧凑代码,在JS中合并非常重要,并且这种行为破坏了这种优势。您必须使用解决方法。如果我问这个问题,那就是这种行为与我的预期相去甚远。
Doc Davluz

1
这是让您知道某个值未分配实际的字符串值和/或缺少toStringvalueOf方法的一种好方法。我可以想象在许多情况下,您希望它们显示为nullundefined显示为空字符串。幸运的是,我几乎没有遇到过这个问题,因为我经常将字符串与join连接起来(它解决了其他问题,例如在使用三元(?)表达式时进行连接)。
jongo45

Answers:


33

将String与潜在的null或未定义对象连接起来的最漂亮的方法是什么,而又不会出现此问题?

有几种方法,您自己也部分提到了它们。简而言之,我能想到的唯一干净的方法是函数:

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

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

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

真的,如果没有封装,您只需要问“最简单”的方式是什么。您问自己这个问题是因为您已经知道自己将要进入一个无法再更改实现的地方,因此您希望它立即变得完美。事实就是如此:需求,观点甚至环境都在变化。他们不断发展。那么,为什么不让自己只用一行或可能要进行一两个测试就可以更改实现呢?

您可以称其为作弊,因为它并没有真正回答如何实现实际的逻辑。但这就是我的观点:没关系。好吧,也许一点。但是,实际上,无需担心,因为更改将变得如此简单。而且由于它不是内联的,所以它看起来也很漂亮-不管您是采用这种方式还是以更复杂的方式来实现。

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

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

当谈到高质量软件开发时,有两点通常被称为反模式。

有人会说这是太多的开销。他们将谈论性能。废话 首先,这几乎不增加开销。如果这是您所担心的,则您选择了错误的语言。甚至jQuery也使用函数。人们需要克服微观优化。

另一件事是:您可以使用代码“ compiler” = minifier。这方面的好工具将尝试检测在编译步骤中内联哪些语句。这样,您就可以保持代码的清洁和可维护性,并且仍然可以保持性能的最后下降,如果您仍然相信它或者确实有一个重要的环境。

最后,对浏览器有一些信心。他们将优化代码,并且如今他们在代码方面做得相当出色。


您的回答是完美的,因为它似乎是解决此问题的更有效,更优雅的方法。我接受它,即使它不能回答前两个问题。@Oriol也应得一些好处,因为他也回答了那些遗漏的问题。
Doc Davluz 2014年

具有讽刺意味的是,当您提出一种导致重复代码的方法时,您会警告重复代码。
更好的Oliver,2014年

@zeroflagL您可以在用例抽象级别上自由封装更多内容。但是我不知道所涉及的用例。我提出一个主意。
IngoBürk2014年

86

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

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

根据规格

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


鉴于,

  • 导致JS转换null或串联时转换undefined为字符串值的奇数背后的历史是什么String

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

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

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

    通常,我认为如果要在字符串中插入一些变量,则显示undefinednull有意义。Eich可能也这么认为。

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

  • 在将来的ECMAScript版本中是否有可能对此行为进行更改?

    很有可能不会。

    既然已经存在Array.prototype.join,那么修改字符串串联的行为只会导致不利因素,而不会带来任何好处。而且,它会破坏旧代码,因此不会向后兼容。

  • 将String与潜力null或连接起来的最漂亮方法是什么undefined

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


我同意这是对问题第三部分的最佳答案。我想投票,但问题有两个部分。再说一次,我想这应该被发布为三个不同的问题。
敏锐2014年

@Cory是的,我已经完成了答案。
Oriol 2014年

21
“您可以使用Array.prototype.join忽略未定义和null” -值得指出的是,只有当您使用空字符串作为joinarg时,此方法才能按需工作。
Madbreaks'Apr 27'15

1
.trim()在之后添加.join(),它将删除前导/尝试空格。(如果使用空格连接)。同样,可以删除其他类型的字符
Fyodor

13

ECMA规范

只是为了充实它在规范中如此行为的原因,此行为自版本1开始就存在。那里的定义和5.1中的定义在语义上是等效的,我将展示5.1的定义。

11.6.1节:加法运算符(+)

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

产生的AdditiveExpressionAdditiveExpression + MultiplicativeExpression的评估如下:

  1. lref为评估AdditiveExpression的结果。
  2. lval为GetValue(lref)。
  3. rref为评估MultiplicativeExpression的结果。
  4. rval为GetValue(rref)。
  5. lprim为ToPrimitive(lval)。
  6. rprim为ToPrimitive(rval)。
  7. 如果Type(lprim)是String或Type(rprim)是String,则
    a。返回字符串即串联的ToString(结果lprim随后的ToString)(rprim
  8. 返回对ToNumber(lprim)和ToNumber(rprim)应用加法运算的结果。请参见下面的注释11.6.3。

因此,如果任何一个值最终都是a 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

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

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

会改变吗?甚至是“怪事”吗?

正如其他人指出的那样,这种更改不太可能改变,因为它会破坏向后兼容性(并没有带来真正的好处),并且考虑到自1997年版规范以来该行为是相同的,甚至更是如此。我也不会真的认为这很奇怪。

如果你要改变这种行为,你会改变的定义ToStringnull,并undefined或将你的特殊情况下这些值的加法运算符? ToString在整个规范中用到了很多很多地方, "null"似乎代表了一个毫无争议的选择null。仅举几个例子,在Java中"" + null是字符串"null",在Python中str(None)是字符串"None"

解决方法

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


2

在将来的ECMAScript版本中是否有可能对此行为进行更改?

我会说机会很小。有以下几个原因:

我们已经知道ES5和ES6的样子

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

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

浏览器做自己的事

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

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

这种变化正在打破

关于标准的最大的事情之一就是它们通常试图向后兼容。对于必须在各种环境中运行相同代码的Web来说尤其如此。

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

当然,此特定更改不太可能破坏很多脚本。但这通常是一个糟糕的论点,因为标准是通用的,必须考虑所有机会。考虑一下

"use strict";

作为切换到严格模式的说明。它显示了标准为使所有内容兼容而付出的巨大努力,因为他们可以将严格模式设置为默认模式(甚至只有默认模式)。但是通过此巧妙的指令,您可以不更改而允许旧代码运行,并且仍然可以利用新的更严格的模式。

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

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

只是没有充分的理由

是的,它使您烦恼。这是可以理解的。但是归根结底,没有什么不是很容易解决的。使用||,编写函数-随便什么。您几乎可以免费使用它。那么,投入全部时间和精力来分析这种我们知道正在打破的变化的真正好处是什么?我只是不明白要点。

Javascript在设计上有几个弱点。随着语言变得越来越重要和强大,它已经成为一个更大的问题。但是,尽管有很好的理由来更改其许多设计,但是其他事情并没有意味着要更改


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


1

要添加null和”,它们需要满足一个最小的通用类型标准,在这种情况下,该标准是字符串类型。

由于这个原因,null转换为“ null”,并且由于它们是字符串,所以将两者串联在一起。

数字也一样:

4 +''='4'

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


5
我知道JS中的合并,但不足以证明这一点。空对象或未定义对象可以合并为空字符串,但我认为这没有问题。你看到一个吗?
Doc Davluz

我认为您的意思是“标准”(来自希腊,用于标准或判断),而不是“标准”(与拉丁语等效,但通常仅用于英语中的一种自行车比赛),实际上取自法国的“critérium”, “竞争”)
Rob Gilliam 2015年

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"
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.