Verkettungsverhalten von JavaScript-Zeichenfolgen mit null oder undefinierten Werten

69

Wie Sie vielleicht wissen, in JavaScript '' + null = "null"und '' + undefined = "undefined"(in den meisten Browsern kann ich testen: Firefox, Chrome und IE). Ich würde gerne wissen, woher diese Kuriosität stammt (was zum Teufel war bei Brendan Eich im Kopf?!) Und ob es ein Ziel gibt, sie in einer zukünftigen Version von ECMA zu ändern. Es ist in der Tat ziemlich frustrierend, 'sthg' + (var || '')Strings mit Variablen zu verketten und ein Framework von Drittanbietern wie Underscore oder ein anderes zu verwenden, um einen Hammer zum Stampfen von Gelee-Nägeln zu verwenden.

Bearbeiten:

Um die von StackOverflow geforderten Kriterien zu erfüllen und meine Frage zu klären, gibt es drei Gründe:

  • Was ist die Geschichte hinter der Seltsamkeit, die JS zur Konvertierung nulloder undefinedzu ihrem Zeichenfolgenwert bei der StringVerkettung führt?
  • Gibt es eine Chance für eine Änderung dieses Verhaltens in zukünftigen ECMAScript-Versionen?
  • Was ist der schönste Weg, um Stringmit Potentialen nulloder undefinedObjekten zu verketten, ohne in dieses Problem zu geraten (einige "undefined"davon "null"in der Mitte des Strings zu bekommen)? Durch die subjektiven Kriterien hübscheste , ich meine: kurz, sauber und effektiv. Keine Notwendigkeit zu sagen, dass '' + (obj ? obj : '')das nicht wirklich hübsch ist ...
Doc Davluz
quelle
+1 Ich stimme zu, ich hatte das gleiche Verhalten erwartet (was mich jetzt viel kostet, wenn der Zustand meine Apps verlangsamt). Ich habe einen RHintergrund und dort bekommen Sie, was Sie gesagt habenpaste0("a", NULL) == "a"
Michele
Eigentlich ist das Verhalten genau das, was ich erwarten würde. null und undefined werden in ihre Zeichenfolgendarstellungen konvertiert, die sich von der leeren Zeichenfolge unterscheiden. Es wäre jedoch einfach genug, eine Funktion zu schreiben, die sich das Argument ansieht und die leere Zeichenfolge zurückgibt, wenn sie null oder undefiniert ist, und den toString von irgendetwas anderem .
Mike Lippert
@ MikeLipper Einfach genug, aber nicht nativ und keine Verschmelzung damit (hoffentlich keine Überladung des Operators in JS). Die Koaleszenz ist in JS für kompakten Code großartig, und dieser Vorteil wird durch dieses Verhalten beeinträchtigt. Sie müssen Problemumgehungen verwenden. Und wenn ich die Frage stelle, ist dieses Verhalten weit von dem entfernt, was ich erwarte.
Doc Davluz
1
Auf diese Weise können Sie wissen lassen, dass einem Wert kein tatsächlicher Zeichenfolgenwert und / oder Mängel toStringoder valueOfMethoden zugewiesen wurden . Ich würde mir vorstellen, dass es so viele Umstände gibt, unter denen Sie möchten, dass sie angezeigt werden, nulloder undefinedwie Sie möchten, dass sie als leere Zeichenfolge angezeigt werden. Glücklicherweise stoße ich kaum auf dieses Problem, da ich Zeichenfolgen häufig mit Join verkette (es löst andere Probleme, z. B. Verkettungen bei Verwendung von ternären ( ?) Ausdrücken).
Jongo45

Antworten:

33

Was ist der schönste Weg, um String mit einem potenziellen Null- oder undefinierten Objekt zu verketten, ohne in dieses Problem zu geraten [...]?

Es gibt verschiedene Möglichkeiten, und Sie haben sie teilweise selbst erwähnt. Um es kurz zu machen, der einzige saubere Weg, den ich mir vorstellen kann, ist eine Funktion:

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

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

Natürlich können (und sollten) Sie die tatsächliche Implementierung an Ihre Bedürfnisse anpassen. Und schon deshalb halte ich diese Methode für überlegen: Sie hat die Kapselung eingeführt.

Wirklich, Sie müssen nur fragen, was der "schönste" Weg ist, wenn Sie keine Kapselung haben. Sie stellen sich diese Frage, weil Sie bereits wissen, dass Sie sich an einen Ort begeben werden, an dem Sie die Implementierung nicht mehr ändern können. Sie möchten also, dass sie sofort perfekt ist. Aber das ist die Sache: Anforderungen, Ansichten und sogar Umgebungen ändern sich. Sie entwickeln sich. Warum erlauben Sie sich nicht, die Implementierung mit nur einer Zeile und vielleicht einem oder zwei Tests zu ändern?

Man könnte dies als Betrug bezeichnen, da es nicht wirklich antwortet, wie die eigentliche Logik implementiert werden soll. Aber das ist mein Punkt: Es spielt keine Rolle. Na ja, vielleicht ein bisschen. Aber wirklich, es besteht kein Grund zur Sorge, weil es so einfach wäre, Änderungen vorzunehmen. Und da es nicht inline ist, sieht es auch viel hübscher aus - ob Sie es auf diese Weise oder auf eine raffiniertere Weise implementieren oder nicht.

Wenn Sie im gesamten Code die ||Inline-Funktion wiederholen, treten zwei Probleme auf:

  • Sie duplizieren Code.
  • Und weil Sie Code duplizieren, ist es schwierig, ihn in Zukunft zu warten und zu ändern.

Und dies sind zwei Punkte, die allgemein als Anti-Patterns bekannt sind, wenn es um qualitativ hochwertige Softwareentwicklung geht.

Einige Leute werden sagen, dass dies zu viel Aufwand ist; Sie werden über Leistung sprechen. Das ist Unsinn. Zum einen erhöht dies kaum den Overhead. Wenn Sie sich darüber Sorgen machen, haben Sie die falsche Sprache gewählt. Sogar jQuery verwendet Funktionen. Die Menschen müssen über die Mikrooptimierung hinwegkommen.

Die andere Sache ist: Sie können einen Code "compiler" = minifier verwenden. Gute Tools in diesem Bereich versuchen zu erkennen, welche Anweisungen während des Kompilierungsschritts inline sind. Auf diese Weise halten Sie Ihren Code sauber und wartbar und können immer noch den letzten Leistungsabfall erzielen, wenn Sie immer noch daran glauben oder wirklich eine Umgebung haben, in der dies wichtig ist.

Schließlich haben Sie etwas Vertrauen in Browser. Sie werden den Code optimieren und machen heutzutage verdammt gute Arbeit.

Ingo Bürk
quelle
Ihre Antwort ist perfekt, da es die effizientere und elegantere Art zu sein scheint, mit diesem Problem umzugehen. Ich akzeptiere es, auch wenn es nicht auf die beiden ersten Punkte antwortet, die verdammt sind. @Oriol hat auch einige Verdienste verdient, weil er auch diese fehlenden Punkte beantwortet.
Doc Davluz
Es ist ironisch, dass Sie vor doppeltem Code warnen, während Sie einen Ansatz präsentieren, der zu doppeltem Code führt.
Ein besserer Oliver
@zeroflagL Sie können noch mehr auf Anwendungsfall-Abstraktionsebene kapseln. Aber ich weiß nichts über Anwendungsfälle. Ich präsentiere eine Idee.
Ingo Bürk
85

Sie können verwenden, um Array.prototype.joinzu ignorieren undefinedund null:

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

Nach der Spezifikation :

Wenn Element ist undefinedoder null, Let neben der leere String sein; Andernfalls sei als nächstes ToString ( Element ).


Angesichts dessen,

  • Was ist die Geschichte hinter der Seltsamkeit, die JS zur Konvertierung nulloder undefinedzu ihrem Zeichenfolgenwert bei der StringVerkettung führt?

    In einigen Fällen ist das aktuelle Verhalten sogar sinnvoll.

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

    Wenn zum Beispiel die obige Funktion ohne Argumente aufgerufen wird, undefined + undefined = NaNist sie wahrscheinlich besser als + = NaN.

    Im Allgemeinen denke ich, dass, wenn Sie einige Variablen in einen String einfügen möchten, das Anzeigen undefinedoder nullSinn macht. Wahrscheinlich hat das auch Eich gedacht.

    Natürlich gibt es Fälle, in denen es besser wäre, diese zu ignorieren, beispielsweise wenn Zeichenfolgen miteinander verbunden werden. Aber für diese Fälle können Sie verwenden Array.prototype.join.

  • Gibt es eine Chance für eine Änderung dieses Verhaltens in zukünftigen ECMAScript-Versionen?

    Höchst wahrscheinlich nicht.

    Da gibt es schon Array.prototype.join Fall , würde das Ändern des Verhaltens der Zeichenfolgenverkettung nur Nachteile, aber keine Vorteile verursachen. Außerdem würde es alte Codes brechen, so dass es nicht abwärtskompatibel wäre.

  • Was ist der schönste Weg, um String mit Potenzial zu verketten? null oderundefined ?

    Array.prototype.joinscheint der einfachste zu sein. Ob es das schönste ist oder nicht, kann meinungsbasiert sein.

Oriol
quelle
Ich stimme zu, dass dies die beste Antwort auf den dritten Teil der Frage ist. Ich wollte positiv stimmen, aber die Frage besteht aus zwei weiteren Teilen. Andererseits stelle ich mir vor, dass dies nur als drei verschiedene Fragen gestellt werden sollte.
Keen
@Cory Richtig, ich habe die Antwort vervollständigt.
Oriol
21
"Sie können Array.prototype.join verwenden, um undefinierte und null zu ignorieren" - es ist erwähnenswert, dass dies nur wie gewünscht funktioniert, wenn Sie eine leere Zeichenfolge als joinArgument verwenden.
Madbreaks
1
Wenn Sie .trim()nach .join()hinzufügen, werden führende Leerzeichen / Testfelder entfernt. (Bei Verbindung mit Leerzeichen). In ähnlicher Weise können andere Arten von Zeichen entfernt werden
Fjodor
13

Die ECMA-Spezifikation

Nur um den Grund zu verdeutlichen, warum es sich in Bezug auf die Spezifikation so verhält, ist dieses Verhalten seit Version eins vorhanden . Die Definition dort und in 5.1 ist semantisch äquivalent, ich werde die 5.1-Definitionen zeigen.

Abschnitt 11.6.1: Der Additionsoperator (+)

Der Additionsoperator führt entweder eine Zeichenfolgenverkettung oder eine numerische Addition durch.

Die Produktion AdditiveExpression : AdditiveExpression + MultiplicativeExpression wird wie folgt bewertet:

  1. Sei lref das Ergebnis der Auswertung von AdditiveExpression.
  2. Sei lval GetValue ( lref ).
  3. Sei rref das Ergebnis der Auswertung von MultiplicativeExpression.
  4. Sei rval GetValue ( rref ).
  5. Sei lprim ToPrimitive ( lval ).
  6. Sei rprim ToPrimitive ( rval ).
  7. Wenn Type ( lprim ) String oder Type ( rprim ) String ist, dann
    a. Gibt den String zurück, der das Ergebnis der Verkettung von ToString ( lprim ) gefolgt von ToString ( rprim ) ist.
  8. Gibt das Ergebnis der Anwendung der Additionsoperation auf ToNumber ( lprim ) und ToNumber ( rprim ) zurück. Siehe den Hinweis unten 11.6.3.

Wenn also einer der Werte a ist String, ToStringwird er für beide Argumente verwendet (Zeile 7) und diese werden verkettet (Zeile 7a). ToPrimitiveGibt alle Nicht-Objektwerte unverändert zurück nullund undefinedbleibt daher unberührt:

Abschnitt 9.1 ToPrimitive

Die abstrakte Operation ToPrimitive verwendet ein Eingabeargument und ein optionales Argument PreferredType . Die abstrakte Operation ToPrimitive konvertiert ihr Eingabeargument in einen Nicht- Objekttyp ... Die Konvertierung erfolgt gemäß Tabelle 10:

Bei allen nicht - ObjectTypen, einschließlich Nullund Undefined, [t]he result equals the input argument (no conversion). so ToPrimitivetut hier nichts.

Schließlich Abschnitt 9.8 ToString

Die abstrakte Operation ToString konvertiert ihr Argument gemäß Tabelle 13 in einen Wert vom Typ String:

Tabelle 13 gibt "undefined"für den UndefinedTyp und "null"für den NullTyp an.

Wird es sich ändern? Ist es überhaupt eine "Kuriosität"?

Wie andere bereits betont haben, ist es sehr unwahrscheinlich, dass sich dies ändert, da dies die Abwärtskompatibilität beeinträchtigen würde (und keinen wirklichen Nutzen bringt), zumal dieses Verhalten seit der Version der Spezifikation von 1997 dasselbe ist. Ich würde es auch nicht wirklich als Kuriosität betrachten.

Wenn Sie dieses Verhalten ändern würden, würden Sie die Definition von ToStringfor ändern nullund undefinedoder würden Sie den Additionsoperator für diese Werte in Sonderfällen festlegen? ToStringwird an vielen, vielen Stellen in der Spezifikation verwendet und "null"scheint eine unumstrittene Wahl für die Darstellung zu sein null. Um nur einige Beispiele zu nennen: In Java "" + nullist die Zeichenfolge "null"und in Python str(None)die Zeichenfolge "None".

Problemumgehung

Andere haben gute Abhilfen gegeben, aber ich möchte hinzufügen , dass ich bezweifle , die Sie verwenden möchten , entity || ""wie Sie Ihre Strategie , da sie beschließt truezu "true"aber falsezu "". Der Array-Join in dieser Antwort weist das eher erwartete Verhalten auf, oder Sie können die Implementierung dieser Antwort ändern, um sie zu überprüfen entity == null(beide null == nullund undefined == nullsind wahr).

Jake Cobb
quelle
2

Gibt es eine Chance für eine Änderung dieses Verhaltens in zukünftigen ECMAScript-Versionen?

Ich würde sagen, die Chancen sind sehr gering. Und es gibt mehrere Gründe:

Wir wissen bereits, wie ES5 und ES6 aussehen

Die zukünftigen ES-Versionen sind bereits fertig oder im Entwurf. Keiner, afaik, ändert dieses Verhalten. Beachten Sie dabei, dass es Jahre dauern wird, bis diese Standards in Browsern in dem Sinne etabliert sind, dass Sie Anwendungen mit diesen Standards schreiben können, ohne sich auf Proxy-Tools verlassen zu müssen, die sie zu tatsächlichem Javascript kompilieren.

Versuchen Sie einfach, die Dauer zu schätzen. Nicht einmal ES5 wird von den meisten Browsern vollständig unterstützt, und es wird wahrscheinlich noch einige Jahre dauern. ES6 ist noch nicht einmal vollständig spezifiziert. Aus heiterem Himmel schauen wir uns noch mindestens fünf Jahre an.

Browser machen ihre eigenen Sachen

Es ist bekannt, dass Browser zu bestimmten Themen ihre eigenen Entscheidungen treffen. Sie wissen nicht, ob alle Browser diese Funktion auf genau dieselbe Weise vollständig unterstützen. Natürlich würden Sie wissen, sobald es Teil des Standards ist, aber selbst wenn es angekündigt wurde, Teil von ES7 zu werden, wäre es ab sofort bestenfalls Spekulation.

Und Browser können hier ihre eigene Entscheidung treffen, insbesondere weil:

Diese Änderung bricht

Eines der größten Dinge an Standards ist, dass sie normalerweise versuchen, abwärtskompatibel zu sein. Dies gilt insbesondere für das Web, in dem derselbe Code für alle Arten von Umgebungen ausgeführt werden muss.

Wenn der Standard eine neue Funktion einführt und diese in alten Browsern nicht unterstützt wird, ist das eine Sache. Bitten Sie Ihren Kunden, seinen Browser für die Nutzung der Website zu aktualisieren. Aber wenn Sie Ihren Browser aktualisieren und plötzlich die Hälfte des Internets für Sie kaputt geht, ist das ein Fehler, ähm-nein.

Sicher, diese spezielle Änderung wird wahrscheinlich nicht viele Skripte beschädigen. Aber das ist normalerweise ein schlechtes Argument, weil ein Standard universell ist und jede Chance berücksichtigen muss. Überlegen Sie einfach

"use strict";

als Anweisung, in den strengen Modus zu wechseln. Es zeigt, wie viel Aufwand ein Standard in den Versuch steckt, alles kompatibel zu machen, weil sie den strengen Modus zum Standard (und sogar nur zum Modus) hätten machen können. Mit dieser cleveren Anweisung können Sie jedoch zulassen, dass alter Code unverändert ausgeführt wird, und trotzdem den neuen, strengeren Modus nutzen.

Ein weiteres Beispiel für die Abwärtskompatibilität: der ===Operator. ==ist grundlegend fehlerhaft (obwohl einige Leute anderer Meinung sind) und es hätte einfach seine Bedeutung ändern können. Stattdessen ===wurde eingeführt, so dass alter Code weiterhin ausgeführt werden kann, ohne zu brechen. Gleichzeitig können neue Programme mit einer strengeren Prüfung geschrieben werden.

Und damit ein Standard die Kompatibilität bricht, muss es einen sehr guten Grund geben. Was uns dazu bringt

Es gibt einfach keinen guten Grund

Ja, es nervt dich. Das ist verständlich. Aber letztlich ist es nichts , das nicht gelöst werden kann , sehr leicht . Verwenden Sie ||, schreiben Sie eine Funktion - was auch immer. Sie können es fast kostenlos zum Laufen bringen. Was ist also wirklich der Vorteil, wenn Sie all die Zeit und Mühe in die Analyse dieser Veränderung investieren, von der wir wissen, dass sie ohnehin bricht? Ich verstehe den Punkt einfach nicht.

Javascript hat mehrere Schwachstellen in seinem Design. Und es ist zunehmend ein größeres Problem geworden, da die Sprache immer wichtiger und mächtiger wurde. Obwohl es sehr gute Gründe gibt, viele seiner Designs zu ändern, sind andere Dinge nicht dazu gedacht, geändert zu werden .


Haftungsausschluss: Diese Antwort basiert teilweise auf Meinungen.

Ingo Bürk
quelle
1

Um null und '' hinzuzufügen, müssen sie ein gemeinsames Mindesttypkriterium erfüllen, das in diesem Fall ein Zeichenfolgentyp ist.

null wird aus diesem Grund in "null" konvertiert und da es sich um eine Zeichenfolge handelt, werden die beiden verkettet.

Das gleiche passiert mit Zahlen:

4 + '' = '4'

Da sich dort eine Zeichenfolge befindet, die nicht in eine beliebige Zahl konvertiert werden kann, wird die 4 stattdessen in eine Zeichenfolge konvertiert.

Mike-O
quelle
5
Ich kenne die Verschmelzung in JS, aber es reicht nicht aus, um dies zu rechtfertigen. Ein null oder undefiniertes Objekt könnte in einem leeren String verschmelzen. Ich sehe kein Problem damit. Siehst du einen
Doc Davluz
Ich denke, Sie meinen eher "Kriterium" (aus dem Griechischen für einen Standard oder ein Urteil) als "Kriterium" (das lateinische Äquivalent, aber normalerweise nur auf Englisch für eine Art Radrennen verwendet, das tatsächlich aus dem französischen "Kriterium" für stammt "Wettbewerb")
Rob Gilliam
0

Warum nicht filtern, um die Richtigkeit zu überprüfen und dann beitreten?

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"
Aymon Fournier
quelle