Hay coerción en el mundo de JavaScript: una historia de detectives
Nathan, no tienes idea de lo que has descubierto.
Llevo semanas investigando esto. Todo comenzó en una noche de tormenta en octubre pasado. Accidentalmente me topé con la Numberclase. Quiero decir, ¿por qué demonios JavaScript tenía una Numberclase?
No estaba preparado para lo que iba a descubrir a continuación.
Resulta que JavaScript, sin decirte, ha estado cambiando tus números a objetos y tus objetos a números justo debajo de tu nariz.
JavaScript esperaba que nadie se diera cuenta, pero la gente ha estado informando un comportamiento extraño e inesperado, y ahora gracias a ti y a tu pregunta, tengo la evidencia que necesito para revelar esto.
Esto es lo que hemos descubierto hasta ahora. No sé si debería decirte esto, es posible que quieras desactivar tu JavaScript.
> function dis() { return this }
undefined
Cuando creó esa función, probablemente no tenía idea de lo que sucedería después. Todo se veía bien, y todo estaba bien, por ahora.
No hay mensajes de error, solo la palabra "indefinido" en la salida de la consola, exactamente lo que cabría esperar. Después de todo, esta fue una declaración de función: no se supone que devuelva nada.
Pero esto fue solo el comienzo. Lo que sucedió después, nadie podría haberlo predicho.
> five = dis.call(5)
Number {[[PrimitiveValue]]: 5}
Sí, lo sé, esperabas un 5, pero eso no es lo que obtuviste, ¿fue algo diferente?
Lo mismo me pasó a mí.
No sabía qué hacer con eso. Me volvió loco. No podía dormir, no podía comer, traté de beberlo, pero ninguna cantidad de Mountain Dew me haría olvidar. ¡Simplemente no tenía ningún sentido!
Fue entonces cuando descubrí lo que realmente estaba sucediendo: fue una coerción, y estaba sucediendo justo en frente de mis ojos, pero estaba demasiado ciego para verlo.
Mozilla intentó enterrarlo poniéndolo donde sabían que nadie miraría: su documentación .
Después de horas de lectura recursiva y relectura y relectura, encontré esto:
"... y los valores primitivos se convertirán en objetos".
Estaba justo ahí, como se puede explicar en la fuente Open Sans. Era la call()función, ¿cómo podría ser tan estúpido?
Mi número ya no era un número en absoluto. En el momento en que lo pasé call(), se convirtió en otra cosa. Se convirtió en ... un objeto.
No podía creerlo al principio. ¿Cómo puede esto ser verdad? Pero no podía ignorar la evidencia que se estaba acumulando a mi alrededor. Está ahí si solo miras:
> five.wtf = 'potato'
"potato"
> five.wtf
"potato"
wtfera correcto. Los números no pueden tener propiedades personalizadas, ¡todos lo sabemos! Es lo primero que te enseñan en la academia.
Deberíamos haber sabido el momento en que vimos la salida de la consola; este no era el número que pensábamos que era. Este era un impostor, un objeto que se hacía pasar por nuestro dulce número inocente.
Esto fue ... new Number(5).
¡Por supuesto! Tenía perfecto sentido. call()tenía un trabajo que hacer, tenía que invocar una función, y para eso necesitaba poblar this, sabía que no podía hacerlo con un número: necesitaba un objeto y estaba dispuesto a hacer cualquier cosa para obtenerlo, incluso si eso significaba coaccionar nuestro número. Cuando call()vio el número 5, vio una oportunidad.
Era el plan perfecto: esperar hasta que nadie estuviera mirando e intercambiar nuestro número por un objeto que se parezca a él. Obtenemos un número, se invoca la función y nadie sería más sabio.
Realmente era el plan perfecto, pero como todos los planes, incluso los perfectos, tenía un agujero y estábamos a punto de caer en él.
Mira, lo call()que no entendió fue que él no era el único en la ciudad que podía obligar a los números. Esto era JavaScript después de todo, la coerción estaba en todas partes.
call() tomé mi número y no iba a parar hasta que le quitara la máscara a su pequeño impostor y lo exponga a toda la comunidad de Stack Overflow.
¿Pero cómo? Necesitaba un plan Claro que parece un número, pero sé que no lo es, debe haber una forma de demostrarlo. ¡Eso es! Se ve como un número, pero puede actuar como tal?
Le dije fiveque necesitaba que fuera 5 veces más grande; no preguntó por qué y no le expliqué. Luego hice lo que cualquier buen programador haría: multipliqué. Seguramente no había forma de que pudiera fingir salir de esto.
> five * 5
25
> five.wtf
'potato'
¡Maldición! No solo se fivemultiplicó bien wtf, todavía estaba allí. Maldito sea este tipo y su papa.
¿Qué demonios está pasando? ¿Estaba equivocado sobre todo esto? ¿Es fiverealmente un número? No, debo estar perdiendo algo, lo sé, hay algo que debo olvidar, algo tan simple y básico que lo estoy pasando por alto por completo.
Esto no se veía bien, había estado escribiendo esta respuesta durante horas y todavía no estaba más cerca de hacer mi punto. No podía seguir así, eventualmente la gente dejaba de leer, tenía que pensar en algo y tenía que pensarlo rápido.
Espera eso es todo! fiveno fue 25, 25 fue el resultado, 25 fue un número completamente diferente. Por supuesto, ¿cómo podría olvidarlo? Los números son inmutables. Cuando multiplica 5 * 5nada, nada se asigna a nada, simplemente crea un nuevo número 25.
Eso debe ser lo que está sucediendo aquí. De alguna manera, cuando multiplico five * 5, fivedebe ser forzado a un número y ese número debe ser el que se usa para la multiplicación. Son los resultados de esa multiplicación los que se imprimen en la consola, no el valor de fivesí mismo. fivenunca se le asigna nada, por lo que, por supuesto, no cambia.
Entonces, ¿cómo puedo fiveasignarme el resultado de una operación? Lo tengo. Antes fiveincluso de tener la oportunidad de pensar, grité "++".
> five++
5
¡Ajá! Lo tuve! Todo el mundo sabe 5 + 1es 6, esta era la evidencia que necesitaba para exponer que fiveno era un número! Fue un impostor! Un mal impostor que no sabía contar. Y podría probarlo. Así es como actúa un número real:
> num = 5
5
> num++
5
¿Espere? ¿Qué estaba pasando aquí? suspiro Me quedé tan atrapado en el revés fiveque olvidé cómo funcionan los operadores de correos. Cuando uso el ++al final de fiveestoy diciendo que devuelva el valor actual, luego incremente five. Es el valor antes de que ocurra la operación lo que se imprime en la consola. numfue de hecho 6y pude demostrarlo:
>num
6
Hora de ver qué fiveera realmente:
>five
6
... era exactamente lo que debería ser. fiveestaba bien, pero estaba mejor. Si fivetodavía fuera un objeto que significaría que todavía tendría la propiedad wtfy estaba dispuesto a apostar todo lo que no.
> five.wtf
undefined
¡Ajá! Yo tenía razón. Lo tuve! fiveahora era un número, ya no era un objeto. Sabía que el truco de la multiplicación no lo salvaría esta vez. Ver five++es realmente five = five + 1. A diferencia de la multiplicación, el ++operador asigna un valor a five. Más específicamente, le asigna los resultados de los five + 1cuales, como en el caso de la multiplicación, devuelve un nuevo número inmutable .
Sabía que lo tenía, y solo para asegurarme de que no pudiera retorcerse. Tenía una prueba más bajo la manga. Si fivetuviera razón, y realmente fuera un número ahora, entonces esto no funcionaría:
> five.wtf = 'potato?'
'potato?'
No me iba a engañar esta vez. Sabía que potato?iba a imprimirse en la consola porque ese es el resultado de la tarea. La verdadera pregunta es, ¿ wtfseguirá allí?
> five.wtf
undefined
Tal como sospechaba, nada, porque a los números no se les pueden asignar propiedades. Aprendimos que el primer año en la academia;)
Gracias nathan Gracias a su coraje al hacer esta pregunta, finalmente puedo dejar todo esto atrás y pasar a un nuevo caso.
Como este sobre la función toValue(). Oh Dios mio. Nooo!
++parece afectar el tipo subyacente