La charla 'Wat' para CodeMash 2012 básicamente señala algunas peculiaridades extrañas con Ruby y JavaScript.
He hecho un JSFiddle de los resultados en http://jsfiddle.net/fe479/9/ .
Los comportamientos específicos de JavaScript (como no sé Ruby) se enumeran a continuación.
En el JSFiddle descubrí que algunos de mis resultados no se correspondían con los del video, y no estoy seguro de por qué. Sin embargo, tengo curiosidad por saber cómo se maneja JavaScript trabajando detrás de escena en cada caso.
Empty Array + Empty Array
[] + []
result:
<Empty String>
Tengo mucha curiosidad sobre el +
operador cuando se usa con matrices en JavaScript. Esto coincide con el resultado del video.
Empty Array + Object
[] + {}
result:
[Object]
Esto coincide con el resultado del video. ¿Que está pasando aqui? ¿Por qué es esto un objeto? ¿Qué hace el +
operador?
Object + Empty Array
{} + []
result:
[Object]
Esto no coincide con el video. El video sugiere que el resultado es 0, mientras que obtengo [Objeto].
Object + Object
{} + {}
result:
[Object][Object]
Esto tampoco coincide con el video, y ¿cómo la salida de una variable da como resultado dos objetos? Tal vez mi JSFiddle está mal.
Array(16).join("wat" - 1)
result:
NaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaN
Hacer wat + 1 da como resultado wat1wat1wat1wat1
...
Sospecho que esto es solo un comportamiento directo que tratar de restar un número de una cadena da como resultado NaN.
fuente
Array(16).join("wat" - 1) + " Batman!"
{} + {}
.Respuestas:
Aquí hay una lista de explicaciones de los resultados que está viendo (y se supone que está viendo). Las referencias que estoy usando son del estándar ECMA-262 .
[] + []
Cuando se utiliza el operador de suma, los operandos izquierdo y derecho se convierten primero en primitivas ( §11.6.1 ). Según §9.1 , la conversión de un objeto (en este caso, una matriz) a una primitiva devuelve su valor predeterminado, que para los objetos con un
toString()
método válido es el resultado de la llamadaobject.toString()
( §8.12.8 ). Para las matrices esto es lo mismo que llamararray.join()
( §15.4.4.2 ). Unir una matriz vacía da como resultado una cadena vacía, por lo que el paso 7 del operador de suma devuelve la concatenación de dos cadenas vacías, que es la cadena vacía.[] + {}
Similar a
[] + []
, ambos operandos se convierten a primitivos primero. Para "Objetos de objeto" (§15.2), este es nuevamente el resultado de la llamadaobject.toString()
, que para objetos no nulos y no definidos"[object Object]"
( §15.2.4.2 ).{} + []
El
{}
aquí no se analiza como un objeto, sino como un bloque vacío ( §12.1 , al menos siempre que no obligue a esa declaración a ser una expresión, pero más sobre eso más adelante). El valor de retorno de los bloques vacíos está vacío, por lo que el resultado de esa declaración es el mismo que+[]
. El+
operador unario ( §11.4.6 ) regresaToNumber(ToPrimitive(operand))
. Como ya sabemos,ToPrimitive([])
es la cadena vacía, y de acuerdo con §9.3.1 ,ToNumber("")
es 0.{} + {}
Similar al caso anterior, el primero
{}
se analiza como un bloque con un valor de retorno vacío. De nuevo,+{}
es lo mismo queToNumber(ToPrimitive({}))
, yToPrimitive({})
es"[object Object]"
(ver[] + {}
). Entonces, para obtener el resultado+{}
, tenemos que aplicarToNumber
en la cadena"[object Object]"
. Al seguir los pasos del §9.3.1 , obtenemosNaN
como resultado:Array(16).join("wat" - 1)
Según §15.4.1.1 y §15.4.2.2 ,
Array(16)
crea una nueva matriz con longitud 16. Para obtener el valor del argumento a unir, §11.6.2 pasos # 5 y # 6 muestran que tenemos que convertir ambos operandos en un número usandoToNumber
.ToNumber(1)
es simplemente 1 ( §9.3 ), mientras que deToNumber("wat")
nuevo esNaN
según §9.3.1 . Siguiendo el paso 7 de §11.6.2 , §11.6.3 dicta queEntonces el argumento de
Array(16).join
esNaN
. Siguiendo §15.4.4.5 (Array.prototype.join
), tenemos que recurrirToString
al argumento, que es"NaN"
( §9.8.1 ):Siguiendo el paso 10 de §15.4.4.5 , obtenemos 15 repeticiones de la concatenación de
"NaN"
y la cadena vacía, lo que equivale al resultado que está viendo. Cuando se usa en"wat" + 1
lugar de"wat" - 1
como argumento, el operador de suma convierte1
a una cadena en lugar de convertir"wat"
a un número, por lo que efectivamente llamaArray(16).join("wat1")
.En cuanto a por qué estás viendo resultados diferentes para el
{} + []
caso: cuando lo usas como un argumento de función, estás forzando que la declaración sea un ExpressionStatement , lo que hace que sea imposible analizarlo{}
como un bloque vacío, por lo que se analiza como un objeto vacío literal.fuente
[]+1
sigue la misma lógica que[]+[]
, solo con el1.toString()
operando rhs. Para[]-1
ver la explicación del"wat"-1
punto 5. Recuerde queToNumber(ToPrimitive([]))
es 0 (punto 3).Esto es más un comentario que una respuesta, pero por alguna razón no puedo comentar tu pregunta. Quería corregir su código JSFiddle. Sin embargo, publiqué esto en Hacker News y alguien sugirió que lo volviera a publicar aquí.
El problema en el código JSFiddle es que
({})
(abrir llaves dentro de paréntesis) no es lo mismo que{}
(abrir llaves como el inicio de una línea de código). Entonces, cuando escribes,out({} + [])
estás obligando{}
a ser algo que no es cuando escribes{} + []
. Esto es parte del 'wat' ness general de Javascript.La idea básica era que JavaScript simple quería permitir ambas formas:
Para hacerlo, se hicieron dos interpretaciones de la llave de apertura: 1. no es obligatorio y 2. puede aparecer en cualquier lugar .
Este fue un movimiento equivocado. El código real no tiene una llave de apertura que aparece en el medio de la nada, y el código real también tiende a ser más frágil cuando usa la primera forma en lugar de la segunda. (Aproximadamente una vez cada dos meses en mi último trabajo, me llamaban al escritorio de un compañero de trabajo cuando sus modificaciones a mi código no funcionaban, y el problema era que habían agregado una línea al "si" sin agregar rizado llaves. Eventualmente acabo de adoptar el hábito de que siempre se requieren llaves, incluso cuando solo escribes una línea).
Afortunadamente, en muchos casos eval () replicará el vatio completo de JavaScript. El código JSFiddle debería leer:
[También es la primera vez que escribo document.writeln en muchos, muchos años, y me siento un poco sucio escribiendo algo relacionado con document.writeln () y eval ().]
fuente
This was a wrong move. Real code doesn't have an opening brace appearing in the middle of nowhere
- No estoy de acuerdo (más o menos): A menudo tengo en los últimos bloques usados como este para variables de ámbito de C . Este hábito se retomó hace un tiempo cuando se hacía C incrustado, donde las variables en la pila ocupan espacio, por lo que si ya no son necesarias, queremos liberar el espacio al final del bloque. Sin embargo, ECMAScript solo abarca dentro de los bloques function () {}. Entonces, aunque no estoy de acuerdo con que el concepto sea incorrecto, estoy de acuerdo en que la implementación en JS es ( posiblemente ) incorrecta.let
para declarar variables de ámbito de bloque.Secundo la solución de @ Ventero. Si lo desea, puede entrar en más detalles sobre cómo
+
convierte sus operandos.El primer paso (§9.1): convertir ambos operandos a primitivas (valores primitivos son
undefined
,null
, booleanos, números, cadenas; todos los demás valores son objetos, incluidas las matrices y funciones). Si un operando ya es primitivo, ya está. Si no, es un objetoobj
y se realizan los siguientes pasos:obj.valueOf()
. Si devuelve un primitivo, ya está. Las instancias directasObject
y las matrices se devuelven, por lo que aún no ha terminado.obj.toString()
. Si devuelve un primitivo, ya está.{}
y[]
ambos devuelven una cadena, así que ya está.TypeError
.Para las fechas, se intercambian los pasos 1 y 2. Puede observar el comportamiento de conversión de la siguiente manera:
Interacción (
Number()
primero se convierte en primitivo y luego en número):Segundo paso (§11.6.1): si uno de los operandos es una cadena, el otro operando también se convierte en cadena y el resultado se produce concatenando dos cadenas. De lo contrario, ambos operandos se convierten en números y el resultado se produce al agregarlos.
Explicación más detallada del proceso de conversión: " ¿Qué es {} + {} en JavaScript? "
fuente
Podemos referirnos a la especificación y eso es genial y más preciso, pero la mayoría de los casos también se pueden explicar de una manera más comprensible con las siguientes afirmaciones:
+
y los-
operadores trabajan solo con valores primitivos. Más específicamente+
(suma) funciona con cadenas o números, y+
(unario) y-
(resta y unario) solo funciona con números.valueOf
otoString
, que están disponibles en cualquier objeto. Esa es la razón por la cual tales funciones u operadores no arrojan errores cuando se invocan en objetos.Entonces podemos decir que:
[] + []
es lo mismoString([]) + String([])
que lo mismo que'' + ''
. Mencioné anteriormente que+
(la suma) también es válida para los números, pero no hay una representación numérica válida de una matriz en JavaScript, por lo que se usa la adición de cadenas.[] + {}
es lo mismoString([]) + String({})
que lo mismo que'' + '[object Object]'
{} + []
. Esta merece más explicación (ver la respuesta de Ventero). En ese caso, las llaves no se tratan como un objeto sino como un bloque vacío, por lo que resulta ser igual que+[]
. Unary+
solo funciona con números, por lo que la implementación intenta obtener un número[]
. Primero intenta lovalueOf
que en el caso de las matrices devuelve el mismo objeto, luego intenta el último recurso: la conversión de untoString
resultado a un número. Podemos escribirlo como+Number(String([]))
cual es igual a+Number('')
cuál es igual que+0
.Array(16).join("wat" - 1)
la resta-
solo funciona con números, por lo que es lo mismo que:,Array(16).join(Number("wat") - 1)
ya"wat"
que no se puede convertir a un número válido. Nos recibeNaN
, y cualquier operación aritmética deNaN
los resultados conNaN
, por lo que tenemos:Array(16).join(NaN)
.fuente
Para reforzar lo que se ha compartido anteriormente.
La causa subyacente de este comportamiento se debe en parte a la naturaleza débilmente tipada de JavaScript. Por ejemplo, la expresión 1 + "2" es ambigua ya que hay dos posibles interpretaciones basadas en los tipos de operandos (int, string) y (int int):
Por lo tanto, con diferentes tipos de entrada, aumentan las posibilidades de salida.
El algoritmo de suma
Las primitivas de JavaScript son string, number, null, undefined y boolean (el símbolo llegará pronto en ES6). Cualquier otro valor es un objeto (por ejemplo, matrices, funciones y objetos). El proceso de coerción para convertir objetos en valores primitivos se describe así:
Si se devuelve un valor primitivo cuando se invoca object.valueOf (), devuelva este valor; de lo contrario, continúe
Si se devuelve un valor primitivo cuando se invoca object.toString (), devuelva este valor; de lo contrario, continúe
Lanzar un error de tipo
Nota: Para valores de fecha, el pedido es invocar toString antes de valueOf.
Si cualquier valor de operando es una cadena, entonces realice una concatenación de cadena
De lo contrario, convierta ambos operandos a su valor numérico y luego agregue estos valores
Conocer los diversos valores de coerción de los tipos en JavaScript ayuda a aclarar los resultados confusos. Ver la tabla de coerción a continuación
También es bueno saber que el operador + de JavaScript es asociativo a la izquierda, ya que esto determina cuáles serán los resultados que involucren más de una operación +.
Aprovechando Así 1 + "2" dará "12" porque cualquier adición que involucre una cadena siempre tendrá por defecto la concatenación de cadenas.
Puede leer más ejemplos en esta publicación de blog (descargo de responsabilidad que lo escribí).
fuente