quitar - template string javascript
Comportamiento de concatenación de cadenas JavaScript con valores nulos o indefinidos (5)
Como sabrá, en JavaScript '''' + null = "null"
y '''' + undefined = "undefined"
(en la mayoría de los navegadores puedo probar: Firefox, Chrome e IE). Me gustaría saber el origen de esta rareza (¿qué diablos le pasó a Brendan Eich ?!) y si hay algún objetivo para cambiarla en una futura versión de ECMA. De hecho, es bastante frustrante tener que hacer ''sthg'' + (var || '''')
para concatenar cadenas con variables y usar un framework de terceros como Underscore u otro para eso está usando un martillo para golpear las uñas con gelatina.
Editar:
Para cumplir con los criterios requeridos por StackOverflow y aclarar mi pregunta, es triple:
- ¿Cuál es la historia detrás de la rareza que hace que JS convierta
null
oundefined
a su valor de cadena enString
concatenación deString
? - ¿Hay alguna posibilidad de un cambio en este comportamiento en futuras versiones de ECMAScript?
- ¿Cuál es la manera más bonita de concatenar
String
conundefined
objetonull
oundefined
potencial sin caer en este problema (obtener un"undefined"
de"null"
en el medio de la Cadena)? Por los criterios subjetivos más bonitos , quiero decir: cortos, limpios y efectivos. No hace falta decir que'''' + (obj ? obj : '''')
no es realmente bonito ...
¿Cuál es la forma más bonita de concatenar Cadena con un objeto nulo o indefinido potencial sin caer en este problema [...]?
Hay varias formas, y usted las mencionó parcialmente. Para abreviar, la única manera limpia que puedo pensar es una función:
var Strings = {};
Strings.orEmpty = function( entity ) {
return entity || "";
};
// usage
var message = "This is a " + Strings.orEmpty( test );
Por supuesto, puede (y debe) cambiar la implementación real para satisfacer sus necesidades. Y esta es la razón por la que creo que este método es superior: introdujo la encapsulación.
Realmente, solo tienes que preguntar cuál es la forma "más bonita", si no tienes encapsulado. Te haces esta pregunta porque ya sabes que te vas a meter en un lugar donde ya no puedes cambiar la implementación, así que quieres que sea perfecto de inmediato. Pero esa es la cuestión: los requisitos, las vistas e incluso los entornos cambian. Ellos evolucionan. Entonces, ¿por qué no te permites cambiar la implementación con tan poco como adaptar una línea y tal vez una o dos pruebas?
Podrías llamar a esto trampa, porque realmente no responde cómo implementar la lógica real. Pero ese es mi punto: no importa. Bueno, tal vez un poco. Pero realmente, no hay necesidad de preocuparse por lo simple que sería cambiar. Y como no está en línea, también se ve mucho más bonito, ya sea que lo implemente de esta manera o de una manera más sofisticada.
Si, a lo largo de tu código, sigues repitiendo el ||
en línea, te encuentras con dos problemas:
- Usted duplica el código.
- Y debido a que duplica el código, hace que sea difícil de mantener y cambiar en el futuro.
Y estos son dos puntos comúnmente conocidos como antipatrones cuando se trata de desarrollo de software de alta calidad.
Algunas personas dirán que esto es demasiado sobrecargado; ellos hablarán sobre el desempeño. No tiene sentido. Por un lado, esto apenas agrega gastos generales. Si esto es lo que le preocupa, eligió el idioma equivocado. Incluso jQuery usa funciones. La gente necesita superar la micro-optimización.
La otra cosa es: puedes usar un código "compilador" = minificador. Las buenas herramientas en esta área intentarán detectar qué enunciados se alinearán durante el paso de compilación. De esta manera, mantendrá su código limpio y mantenible, y aún puede obtener la última caída de rendimiento si todavía cree en él o si realmente tiene un entorno donde esto importa.
Por último, ten fe en los navegadores. Van a optimizar el código y hacen un muy buen trabajo en estos días.
¿Hay alguna posibilidad de un cambio en este comportamiento en futuras versiones de ECMAScript?
Yo diría que las posibilidades son muy escasas. Y hay varias razones:
Ya sabemos cómo son ES5 y ES6
Las futuras versiones de ES ya están hechas o en borrador. Ninguno de los dos, afaik, cambia este comportamiento. Y lo que hay que tener en cuenta aquí es que llevará años establecer estos estándares en los navegadores en el sentido de que puede escribir aplicaciones con estos estándares sin depender de herramientas de proxy que compilen el Javascript real.
Solo trata de estimar la duración. Ni siquiera ES5 cuenta con el respaldo total de la mayoría de los navegadores que existen y probablemente demore unos años más. ES6 aún no está completamente especificado. De la nada, estamos viendo al menos otros cinco años.
Los navegadores hacen sus propias cosas
Los navegadores son conocidos por tomar sus propias decisiones sobre ciertos temas. No sabe si todos los navegadores soportan completamente esta característica exactamente de la misma manera. Por supuesto, usted sabrá una vez que es parte del estándar, pero a partir de ahora, incluso si se anunció que formará parte de ES7, solo sería una especulación en el mejor de los casos.
Y los navegadores pueden tomar su propia decisión aquí especialmente porque:
Este cambio se está rompiendo
Una de las cosas más importantes de los estándares es que generalmente intentan ser compatibles con versiones anteriores. Esto es especialmente cierto para la web donde el mismo código debe ejecutarse en todo tipo de entornos.
Si el estándar introduce una nueva función y no es compatible con navegadores antiguos, eso es una cosa. Dígale a su cliente que actualice su navegador para usar el sitio. Pero si actualiza su navegador y de repente la mitad de los cortes de internet para usted, eso es un error uhm-no.
Claro, es poco probable que este cambio en particular rompa muchos guiones. Pero eso suele ser un argumento pobre porque un estándar es universal y debe tener en cuenta todas las posibilidades. Solo considera
"use strict";
como la instrucción de cambiar al modo estricto. Muestra todo el esfuerzo que un estándar pone en tratar de hacer que todo sea compatible, porque podrían haber hecho del modo estricto el modo predeterminado (e incluso el único). Pero con esta instrucción inteligente, permite que el código viejo se ejecute sin cambios y aún así puede aprovechar el nuevo y más estricto modo.
Otro ejemplo de compatibilidad con versiones anteriores: el operador ===
. ==
es fundamentalmente defectuoso (aunque algunas personas no están de acuerdo) y podría haber cambiado su significado. En su lugar, se introdujo ===
, lo que permite que el código viejo se ejecute sin interrupción; al mismo tiempo, permite que se escriban nuevos programas usando un control más estricto.
Y para que un estándar rompa la compatibilidad, tiene que haber una muy buena razón. Lo que nos lleva a
Simplemente no hay una buena razón
Sí, te molesta. Eso es comprensible. Pero, en última instancia, no es nada que no se pueda resolver muy fácilmente . Usar ||
, escribe una función, lo que sea. Puedes hacerlo funcionar casi sin costo. Entonces, ¿cuál es realmente el beneficio de invertir todo el tiempo y esfuerzo en analizar este cambio que sabemos que se está rompiendo de todos modos? Simplemente no veo el punto.
Javascript tiene varios puntos débiles en su diseño. Y se ha convertido cada vez más en un problema mayor a medida que el idioma se volvió cada vez más importante y poderoso. Pero si bien hay muy buenas razones para cambiar mucho su diseño, otras cosas simplemente no están destinadas a ser cambiadas .
Descargo de responsabilidad: esta respuesta se basa en parte en las opiniones.
La especificación de ECMA
Para dar cuerpo a la razón por la que se comporta de esta manera en términos de especificaciones, este comportamiento ha estado presente desde la primera versión . La definición allí y en 5.1 son semánticamente equivalentes, mostraré las definiciones 5.1.
Sección 11.6.1: El operador de adición (+)
El operador de adición realiza una concatenación de cadenas o una suma numérica.
La producción AdditiveExpression : AdditiveExpression + MultiplicativeExpression se evalúa de la siguiente manera:
- Deje que lref sea el resultado de evaluar AdditiveExpression.
- Deje lval ser GetValue ( lref ).
- Deje que rref sea el resultado de la evaluación de Expresión multiplicativa.
- Deje que rval sea GetValue ( rref ).
- Deje que lprim sea ToPrimitive ( lval ).
- Deje que rprim sea ToPrimitive ( rval ).
- Si Tipo ( lprim ) es Cadena o Tipo ( rprim ) es Cadena, entonces
a. Devuelve la cadena que es el resultado de concatenar ToString ( lprim ) seguido de ToString ( rprim )- Devuelve el resultado de aplicar la operación de adición a ToNumber ( lprim ) y ToNumber ( rprim ). Ver la Nota debajo 11.6.3.
Entonces, si cualquier valor termina siendo una String
, entonces ToString
se usa en ambos argumentos (línea 7) y aquellos se concatenan (línea 7a). ToPrimitive
devuelve todos los valores no objeto sin cambios, por lo que undefined
se modifican los valores null
e undefined
:
La operación abstracta ToPrimitive toma un argumento de entrada y un argumento opcional PreferredType . La operación abstracta ToPrimitive convierte su argumento de entrada a un tipo que no es un objeto ... La conversión se produce de acuerdo con la Tabla 10:
Para todos los tipos que no sean Object
, incluidos tanto Null
como Undefined
, [t]he result equals the input argument (no conversion).
Así que ToPrimitive
no hace nada aquí.
Finalmente, la Sección 9.8 ToString
La operación abstracta ToString convierte su argumento en un valor de tipo String de acuerdo con la Tabla 13:
La Tabla 13 muestra "undefined"
para el tipo Undefined
y "null"
para el tipo Null
.
¿Cambiará? ¿Es incluso una "rareza"?
Como han señalado otros, es poco probable que esto cambie ya que rompería la compatibilidad con versiones anteriores (y no aportaría ningún beneficio real), más aún dado que este comportamiento es el mismo desde la versión de 1997 de la especificación. Yo tampoco lo consideraría una rareza.
Si tuviera que cambiar este comportamiento, ¿cambiaría la definición de ToString
por null
e undefined
o sería un caso especial el operador de suma para estos valores? ToString
se usa en muchos, muchos lugares a lo largo de la especificación y "null"
parece ser una elección indiscutible para representar null
. Solo para dar un par de ejemplos, en Java "" + null
es la cadena "null"
y en Python str(None)
es la cadena "None"
.
Solución
Otros han dado buenas soluciones, pero agregaría que dudo que quiera usar entity || ""
entity || ""
como estrategia, ya que se convierte en true
en "true"
pero false
en ""
. La combinación de matriz en esta respuesta tiene el comportamiento más esperado, o puede cambiar la implementación de esta respuesta para verificar entity == null
(tanto null == null
como undefined == null
son verdaderos).
Para agregar null y '''' deben cumplir un criterium de tipo común mínimo que en este caso es un tipo de cadena.
null se convierte en "nulo" por este motivo y como son cadenas, los dos se concatenan.
Lo mismo sucede con los números:
4 + '''' = ''4''
ya que hay una cadena allí que no se puede convertir a ningún número, por lo que el 4 se convertirá a cadena en su lugar.
Puede usar Array.prototype.join
para ignorar undefined
y null
:
[''a'', ''b'', void 0, null, 6].join(''''); // ''ab6''
De acuerdo con la spec :
Si el elemento
undefined
estáundefined
o esnull
, Let the next be the Empty String; de lo contrario, deja que sea ToString ( elemento ).
Dado que,
¿Cuál es la historia detrás de la rareza que hace que JS convierta
null
oundefined
a su valor de cadena enString
concatenación deString
?De hecho, en algunos casos, el comportamiento actual tiene sentido.
function showSum(a,b) { alert(a + '' + '' + b + '' = '' + (+a + +b)); }
Por ejemplo, si se llama a la función anterior sin argumentos,
undefined + undefined = NaN
probablemente sea mejor que+ = NaN
.En general, creo que si quiere insertar algunas variables en una cadena, tiene sentido mostrar
undefined
onull
. Probablemente, Eich pensó eso también.Por supuesto, hay casos en los que ignorarlos sería mejor, como cuando se unen cadenas. Pero para esos casos puedes usar
Array.prototype.join
.¿Hay alguna posibilidad de un cambio en este comportamiento en futuras versiones de ECMAScript?
Probablemente no.
Como ya existe
Array.prototype.join
, modificar el comportamiento de la concatenación de cadenas solo generaría inconvenientes, pero no ventajas. Además, rompería los códigos antiguos, por lo que no sería retrocompatible.¿Cuál es la forma más bonita de concatenar Cadena con potencial
null
oundefined
?Array.prototype.join
parece ser el más simple. Si es el más lindo o no, puede basarse en las opiniones.