web-dev-qa-db-fra.com

Comportement de concaténation de chaînes JavaScript avec des valeurs NULL ou indéfinies

Comme vous le savez peut-être, en JavaScript '' + null = "null" Et '' + undefined = "undefined" (Dans la plupart des navigateurs, je peux tester: Firefox, Chrome et IE). J'aimerais connaître la Origine de cette bizarrerie (qu'est-ce qui se passait dans la tête de Brendan Eich?!) Et s'il y a un quelconque objectif de la modifier dans une future version d'ECMA, il est en effet assez frustrant de devoir faire 'sthg' + (var || '') pour concaténer Les chaînes avec des variables et en utilisant un framework tiers comme Underscore ou autre pour cela utilisent un marteau pour marteler les ongles en gelée.

Edit:

Pour répondre aux critères requis par StackOverflow et clarifier ma question, la question est triple:

  • Quelle est l'histoire derrière la bizarrerie qui fait que JS convertit null ou undefined en valeur de chaîne dans la concaténation String?
  • Existe-t-il une chance pour que ce comportement change dans les futures versions d'ECMAScript?
  • Quelle est la plus jolie façon de concaténer un objet String avec un objet null ou undefined potentiel sans tomber dans ce problème (obtenir un certain "undefined" De "null" au milieu de la chaîne)? Par les critères subjectifs les plus jolis , j'entends: court, net et efficace. Inutile de dire que '' + (obj ? obj : '') n'est pas vraiment jolie…
54
PomCompot

Quel est le meilleur moyen de concaténer String avec un objet null ou indéfini potentiel sans tomber dans ce problème [...]?

Il y a plusieurs façons et vous les avez partiellement mentionnées vous-même. Pour faire court, la manière propre à laquelle je peux penser est une fonction:

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

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

Bien sûr, vous pouvez (et devriez) modifier l’implémentation réelle en fonction de vos besoins. Et c’est déjà pourquoi je pense que cette méthode est supérieure: elle a introduit l’encapsulation.

En réalité, il vous suffit de demander quelle est la "plus jolie" méthode, si vous n'avez pas d'encapsulation. Vous vous posez cette question parce que savez déjà que vous allez vous mettre dans un endroit où vous ne pouvez plus modifier l'implémentation, vous voulez donc qu'elle être parfait tout de suite. Mais c’est la chose: les exigences, les points de vue et même les environnements changent. Ils évoluent. Alors, pourquoi ne pas vous permettre de modifier l’implémentation en adaptant aussi peu une ligne et éventuellement un ou deux tests?

On pourrait appeler cela de la triche, car cela ne dit pas vraiment comment implémenter la logique réelle. Mais c'est ce que je veux dire: peu importe. Eh bien, peut-être un peu. Mais vraiment, il n'y a pas de quoi s'inquiéter à cause de la simplicité de son changement. Et comme il n'est pas en ligne, il a l'air beaucoup plus joli, que vous l'implémentiez ou non de cette façon ou de manière plus sophistiquée.

Si, tout au long de votre code, vous continuez à répéter le || inline, vous rencontrez deux problèmes:

  • Vous dupliquez le code.
  • Et comme vous dupliquez du code, vous empêchez la maintenance et les modifications futures.

Et ce sont deux points communément connus pour être anti-modèles en matière de développement logiciel de haute qualité.

Certaines personnes diront que c'est trop de frais généraux; ils vont parler de performance. C'est n'importe quoi. D'une part, cela ajoute à peine des frais généraux. Si c'est ce qui vous inquiète, vous avez choisi la mauvaise langue. Même jQuery utilise des fonctions. Les gens ont besoin de surmonter la micro-optimisation.

L'autre chose est: vous pouvez utiliser un code "compilateur" = minifier. Les bons outils dans ce domaine essaieront de détecter les instructions à intégrer lors de la compilation. De cette façon, vous gardez votre code propre et maintenable et pouvez toujours obtenir cette dernière baisse de performance si vous y croyez toujours ou si vous avez vraiment un environnement où cela compte.

Enfin, faites confiance aux navigateurs. Ils vont optimiser le code et ils font un très bon travail ces derniers temps.

28
Ingo Bürk

Vous pouvez utiliser Array.prototype.join ignorer undefined et null:

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

Selon le spec :

Si l'élément est undefined ou null, laissez suivant soit la chaîne vide; sinon, passons à côté du élément ToString (, élément ).


Étant donné que,

  • Quelle est l'histoire derrière la bizarrerie qui fait que JS convertit null ou undefined en valeur de chaîne dans la concaténation String?

    En fait, dans certains cas, le comportement actuel a du sens.

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

    Par exemple, si la fonction ci-dessus est appelée sans arguments, undefined + undefined = NaN est probablement mieux que + = NaN.

    En général, je pense que si vous voulez insérer des variables dans une chaîne, afficher undefined ou null a du sens. Probablement, Eich a pensé cela aussi.

    Bien sûr, il y a des cas dans lesquels ignorer ceux-là serait mieux, par exemple lors de la jonction de chaînes. Mais pour ces cas, vous pouvez utiliser Array.prototype.join.

  • Existe-t-il une chance pour que ce comportement change dans les futures versions d'ECMAScript?

    Préférablement pas.

    Puisqu'il y a déjà Array.prototype.join, modifier le comportement de la concaténation de chaînes ne causerait que des inconvénients, mais aucun avantage. De plus, cela casserait les anciens codes, ce qui éviterait une compatibilité ascendante.

  • Quel est le meilleur moyen de concaténer String avec un potentiel null ou undefined?

    Array.prototype.join semble être le plus simple. Que ce soit la plus jolie ou pas peut être basé sur l'opinion.

68
Oriol

La spécification ECMA

Juste pour préciser la raison pour laquelle il se comporte de la sorte en termes de spécification, ce comportement est présent depuis version one . La définition ici et en 5.1 sont sémantiquement équivalentes, je vais montrer les définitions 5.1.

Section 11.6.1: L'opérateur d'addition (+)

L'opérateur d'addition effectue une concaténation de chaîne ou une addition numérique.

La production AdditiveExpression : AdditiveExpression + MultiplicativeExpression est évalué comme suit:

  1. Soit lref le résultat de l’évaluation de AdditiveExpression.
  2. Soit lval soit GetValue ( lref ).
  3. Soit rref le résultat de l'évaluation de MultiplicativeExpression.
  4. Soit rval soit GetValue ( rref ).
  5. Soit lprim la valeur de Primitive ( lval ).
  6. Soit rprim la valeur de Primitive ( rval ).
  7. Si Type ( lprim ) est une chaîne ou un type ( rprim ) est une chaîne , puis
    une. Renvoie la chaîne résultant de la concaténation de ToString ( lprim ) suivi de ToString ( rprim )
  8. Renvoie le résultat de l'application de l'opération d'addition à ToNumber ( lprim ) et ToNumber ( rprim ). Voir la note ci-dessous 11.6.3.

Donc, si l'une ou l'autre valeur finit par être un String, alors ToString est utilisé sur les deux arguments (ligne 7) et ceux-ci sont concaténés (ligne 7a). ToPrimitive renvoie toutes les valeurs non-objets inchangées, donc null et undefined ne sont pas modifiées:

Section 9.1 ToPrimitive

L'opération abstraite ToPrimitive prend un argument d'entrée et un argument facultatif PreferredType . L'opération abstraite ToPrimitive convertit son argument en entrée en un type non-objet ... La conversion a lieu conformément au tableau 10:

Pour tous les types non -Object, y compris Null et Undefined, [t]he result equals the input argument (no conversion). Donc, ToPrimitive ne fait rien ici.

Enfin, Section 9.8 ToString

L'opération abstraite ToString convertit son argument en une valeur de type String conformément au tableau 13:

Le tableau 13 donne "undefined" Pour le type Undefined et "null" Pour le type Null.

Ça va changer? Est-ce même une "bizarrerie"?

Comme d'autres l'ont souligné, il est très peu probable que cela change car cela annulerait la compatibilité en amont (et n'apporterait aucun avantage réel), d'autant plus que ce comportement est le même depuis la version de 1997 de la spécification. Je ne considérerais pas vraiment cela comme une bizarrerie.

Si vous deviez changer ce comportement, changeriez-vous la définition de ToString pour null et undefined ou mettriez-vous en cas spécial l'opérateur d'addition pour ces valeurs? ToString est utilisé à de très nombreux endroits dans la spécification et "null" semble être un choix non controversé pour représenter null. Juste pour donner quelques exemples, dans Java "" + null Est la chaîne "null" Et dans Python str(None) est la chaîne "None".

Workaround

D'autres ont donné de bonnes solutions de contournement, mais j'ajouterais que je doute que vous souhaitiez utiliser entity || "" Comme stratégie, car cela résout true en "true" Mais false en "". Le tableau rejoindre dans cette réponse a le comportement le plus attendu, ou vous pouvez modifier l'implémentation de cette réponse pour vérifier entity == null (Les deux null == null et undefined == null sont vrais).

6
Jake Cobb

Existe-t-il une chance pour que ce comportement change dans les futures versions d'ECMAScript?

Je dirais que les chances sont très minces. Et il y a plusieurs raisons:

Nous savons déjà à quoi ressemblent les ES5 et ES6

Les futures versions ES sont déjà terminées ou en projet. Ni l'un ni l'autre, autant que je sache, ne modifie ce comportement. Il est important de noter que cela prendra des années pour que ces normes soient établies dans les navigateurs, en ce sens que vous pouvez écrire des applications avec ces normes sans avoir recours à des outils de proxy qui le compilent en Javascript.

Essayez juste d'estimer la durée. Même la plupart des navigateurs ne prennent pas totalement en charge la version ES5 et cela prendra probablement quelques années de plus. ES6 n'est même pas encore complètement spécifié. À l'improviste, nous prévoyons au moins cinq ans.

Les navigateurs font leurs propres choses

Les navigateurs sont connus pour prendre leurs propres décisions sur certains sujets. Vous ne savez pas si tous les navigateurs prendront pleinement en charge cette fonctionnalité de la même manière. Bien sûr, vous le sauriez une fois que cela fait partie de la norme, mais à partir de maintenant, même s'il était annoncé qu'il ferait partie de ES7, ce ne serait que de la spéculation.

Et les navigateurs peuvent prendre leur propre décision ici surtout parce que:

Ce changement se brise

L'un des principaux avantages des normes est qu'elles essaient généralement d'essayer d'être rétro-compatibles. Ceci est en particulier vrai pour le Web où le même code doit être exécuté sur tous les types d'environnements.

Si la norme introduit une nouvelle fonctionnalité et qu'elle n'est pas prise en charge par les anciens navigateurs, c'est une chose. Dites à votre client de mettre à jour son navigateur pour utiliser le site. Mais si vous mettez à jour votre navigateur et que soudainement la moitié de l’Internet tombe en panne pour vous, c’est un bogue….

Bien sûr, il est peu probable que ce changement particulier casse beaucoup de scripts. Mais c'est généralement un argument de piètre qualité, car une norme est universelle et doit tenir compte de toutes les chances. Il suffit de considérer

"use strict";

comme instruction de passer en mode strict. Cela montre bien les efforts fournis par une norme pour essayer de rendre tout compatible, car elle aurait pu faire du mode strict le mode par défaut (et même le seul mode). Mais avec cette instruction astucieuse, vous permettez à l'ancien code de fonctionner sans changement et pouvez toujours tirer parti du nouveau mode, plus strict.

Un autre exemple de compatibilité ascendante: le === _ opérateur. == est fondamentalement défectueux (même si certaines personnes ne sont pas d’accord) et cela aurait pu changer sa signification. Au lieu, === a été introduit, permettant à l'ancien code de s'exécuter sans casser; permet en même temps d’écrire de nouveaux programmes en utilisant un contrôle plus strict.

Et pour qu'un standard puisse briser la compatibilité, il doit y avoir une très bonne raison. Ce qui nous amène à

Il n'y a pas de bonne raison

Oui, ça vous dérange. C'est compréhensible. Mais finalement, rien ne peut être résolu très facilement . Utilisation ||, écrivez une fonction - peu importe. Vous pouvez le faire fonctionner presque sans frais. Alors, quel est vraiment l'avantage d'investir tout le temps et tous les efforts consacrés à l'analyse de ce changement qui, nous le savons, est en train de se produire? Je ne vois tout simplement pas le but.

Javascript a plusieurs points faibles dans sa conception. Et le problème est devenu de plus en plus important au fur et à mesure que la langue devenait de plus en plus importante et puissante. Mais bien qu'il y ait de très bonnes raisons de changer beaucoup de conception, , d'autres éléments ne sont simplement pas censés être modifiés .


Disclaimer: Cette réponse est en partie basée sur les opinions.

2
Ingo Bürk

Pour ajouter null et '', ils doivent répondre à un critère de type commun minimum qui, dans ce cas, est un type de chaîne.

null est converti en "null" pour cette raison et, comme ils sont chaîne, les deux sont concaténés.

La même chose arrive avec les chiffres:

4 + '' = '4'

comme il y a une chaîne qui ne peut être convertie en un nombre, le 4 sera converti en chaîne à la place.

1
Mike-O