¿Qué está sucediendo en este código con objetos numéricos que poseen propiedades e incrementan el número?

246

Un tweet reciente contenía este fragmento de JavaScript.

¿Alguien puede explicar lo que está sucediendo paso a paso?

> function dis() { return this }
undefined
> five = dis.call(5)
Number {[[PrimitiveValue]]: 5}
> five.wtf = 'potato'
"potato"
> five.wtf
"potato"
> five * 5
25
> five.wtf
"potato"
> five++
5
> five.wtf
undefined
> five.wtf = 'potato?'
"potato?"
> five.wtf
undefined
> five
6

En particular, no me queda claro:

  • por qué el resultado de dis.call(5)es un Numbercon algún tipo de [[PrimitiveValue]]propiedad, pero los resultados de five++y five * 5parecen ser solo los números simples 5y 25(no Numbers)
  • por qué la five.wtfpropiedad desaparece después del five++incremento
  • por qué la five.wtfpropiedad ya no es configurable después del five++incremento, a pesar de que la five.wtf = 'potato?'asignación aparentemente establece el valor.
Nathan Long
fuente
66
Jaja vi ese tweet !! Es tan extraño, creo que porque lo multiplicas, no afecta del todo el objeto, pero ++parece afectar el tipo subyacente
Callum Linington
8
¿Qué línea no entendiste? Prevs Postincremento? Después de la multiplicación, nuevamente es un Numberobjeto que no tiene wtfpropiedad ... Pero aún así es un objeto objectque puede tener properties...
Rayon
25
Cuando llama discon dis.call(5), esto envuelve el número primitivo 5en un objeto de tipo Numberque contiene el 5, para que este objeto pueda ser devuelto como thisobjeto. La ++convierte de nuevo a un número primitivo que no puede contener propiedades, por lo que wtfcomienza a ser indefinido.
GSerg
55
@Rayon ... y eso ha cambiado con ES6 . Así que en el futuro todo esto dejará de suceder
GSerg

Respuestas:

278

OP aquí. Es divertido ver esto en Stack Overflow :)

Antes de pasar por el comportamiento, es importante aclarar algunas cosas:

  1. El valor numérico y el objeto numérico ( a = 3vs a = new Number(3)) son muy diferentes. Uno es primitivo, el otro es un objeto. No puede asignar atributos a primitivas, pero puede asignar a objetos.

  2. La coerción entre los dos es implícita.

    Por ejemplo:

    (new Number(3) === 3)  // returns false
    (new Number(3) == 3)   // returns true, as the '==' operator coerces
    (+new Number(3) === 3) // returns true, as the '+' operator coerces
  3. Cada expresión tiene un valor de retorno. Cuando REPL lee y ejecuta una expresión, esto es lo que muestra. Los valores de retorno a menudo no significan lo que piensas e implican cosas que simplemente no son ciertas.

Está bien, aquí vamos.

Imagen original del código JavaScript.

La prenda.

> function dis() { return this }
undefined
> five = dis.call(5)
[Number: 5]

Definir una función disy llamarla con 5. Esto ejecutará la función con 5el contexto ( this). Aquí se coacciona de un valor de Número a un objeto de Número. Es muy importante tener en cuenta que si estuviéramos en modo estricto, esto no habría sucedido .

> five.wtf = 'potato'
'potato'
> five.wtf
'potato'

Ahora establecemos el atributo five.wtfen 'potato', y con cinco como objeto, efectivamente acepta la Asignación simple .

> five * 5
25
> five.wtf
'potato'

Con fivecomo objeto, me aseguro de que aún pueda realizar operaciones aritméticas simples. Puede. ¿Sus atributos aún se mantienen? Si.

El turno.

> five++
5
> five.wtf
undefined

Ahora lo comprobamos five++. El truco con el incremento de postfix es que toda la expresión se evaluará contra el valor original y luego incrementará el valor. Parece que fivetodavía son cinco, pero realmente la expresión se evaluó en cinco, luego se estableció fiveen 6.

No solo fivese configuró en 6, sino que se volvió a convertir en un valor de Número y todos los atributos se pierden. Como las primitivas no pueden contener atributos, five.wtfno está definido.

> five.wtf = 'potato?'
'potato?'
> five.wtf
undefined

De nuevo intento reasignar un atributo wtfa five. El valor de retorno implica que se mantiene, pero de hecho no lo hace porque fivees un valor de Número, no un objeto de Número. La expresión se evalúa como 'potato?', pero cuando verificamos vemos que no se asignó.

El prestigio.

> five
6

Desde el incremento de postfix, fiveha sido 6.

Mateo
fuente
70
¿Estás mirando de cerca?
Waddles
3
@Nathan Long Bueno, en Java, que JavaScript tomó prestado en gran medida desde el principio, todas las primitivas tienen un equivalente de clase. inty Integer, por ejemplo. Supongo que esto es para que pueda crear una función doSomething(Object)y aún así poder darle primitivas. Las primitivas se convertirán a sus clases correspondientes en este caso. Pero a JS realmente no le importan los tipos, por lo que la razón es probablemente otra cosa
Suppen
44
@Eric Lo primero que debe ++hacer es aplicar ToNumber al valor . Compare un caso similar con cadenas: si tiene x="5", x++devuelve el número 5.
apsillers
2
Realmente la magia ocurre dis.call(5), la coerción para objetar, nunca hubiera esperado eso.
mcfedr
3
@gman, excepto que hay muchas influencias mucho más detalladas, incluido el nombramiento de grandes fragmentos de su biblioteca estándar, el comportamiento de los tipos de objetos estándar e incluso el hecho de que usa una sintaxis de llaves y punto y coma (incluso si los puntos y comas son opcional) deriva del hecho de que fue diseñado para que los programadores de Java lo conozcan. Sí, es confuso para los principiantes. Eso no significa que las influencias no existan.
Julio
77

Hay dos formas diferentes de representar un número:

var a = 5;
var b = new Number(5);

El primero es un primitivo, el segundo un objeto. Para todos los efectos, ambos se comportan igual, excepto que se ven diferentes cuando se imprimen en la consola. Una diferencia importante es que, como objeto, new Number(5)acepta nuevas propiedades como cualquier plano {}, mientras que la primitiva 5no:

a.foo = 'bar';  // doesn't stick
b.foo = 'bar';  // sticks

En cuanto a la dis.call(5)parte inicial , consulte ¿Cómo funciona la palabra clave "this"? . Digamos que el primer argumento de callse usa como el valor de this, y que esta operación fuerza el número a la Numberforma de objeto más compleja . * Más tarde lo ++fuerza a volver a la forma primitiva, porque la operación de suma +resulta en una nueva primitiva.

> five = dis.call(5)  // for all intents and purposes same as new Number(5)
Number {[[PrimitiveValue]]: 5}
> five.wtf = 'potato'
"potato"
> five.wtf
"potato"

Un Numberobjeto acepta nuevas propiedades.

> five++

++da como resultado un nuevo 6valor primitivo ...

> five.wtf
undefined
> five.wtf = 'potato?'
"potato?"
> five.wtf
undefined

... que no tiene y no acepta atributos personalizados.

* Tenga en cuenta que en modo estricto el thisargumento se trataría de manera diferente y no se convertiría a a Number. Ver http://es5.github.io/#x10.4.3 para los detalles de implementación.

difunto
fuente
2
@Pharap seguro mucho mejor en C / C ++ donde puedes hacer #define true false. O en Java, donde puedes redefinir qué significan los números. Estos son buenos lenguajes sólidos. En verdad, cada idioma tiene "trucos" similares en los que obtienes un resultado que funciona según lo previsto pero que puede parecer extraño.
VLAZ
1
@Vld Ok déjame reformular eso, es por eso que odio escribir pato.
Pharap
59

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!

Luis perez
fuente
99
Olvídalo, Jake; Es Javascript.
Seth
¿Por qué llamamos a las funciones de constructor "clases" en JS?
evolutionxbox
27
01 > function dis() { return this }
02 undefined
03 > five = dis.call(5)
04 Number {[[PrimitiveValue]]: 5}
05 > five.wtf = 'potato'
06 "potato"
07 > five.wtf
08 "potato"
09 > five * 5
10 25
11 > five.wtf
12 "potato"
13 > five++
14 5
15 > five.wtf
16 undefined
17 > five.wtf = 'potato?'
18 "potato?"
19 > five.wtf
20 undefined
21 > five
22 6

01declara una función disque devuelve el objeto de contexto. Lo que thisrepresenta cambios dependiendo de si está utilizando el modo estricto o no. Todo el ejemplo tiene resultados diferentes si la función se declara como:

> function dis() { "use strict"; return this }

Esto se detalla en la sección 10.4.3 de la especificación ES5

  1. Si el código de función es estricto, establezca ThisBinding en thisArg.
  2. De lo contrario, si thisArg es nulo o no está definido, establezca ThisBinding en el objeto global.
  3. De lo contrario, si Tipo (thisArg) no es Object, establezca ThisBinding en ToObject (thisArg).

02es el valor de retorno de la declaración de función. undefineddebería explicarse por sí mismo aquí.

03la variable fivese inicializa con el valor de retorno de discuando se llama en el contexto del valor primitivo 5. Como disno está en modo estricto, esta línea es idéntica a la llamada five = Object(5).

04El Number {[[PrimitiveValue]]: 5}valor de retorno impar es la representación del objeto que envuelve el valor primitivo.5

05A la propiedad fivedel objeto wtfse le asigna un valor de cadena de'potato'

06 es el valor de retorno de la tarea y debe explicarse por sí mismo.

07la fivedel objeto wtfpropiedad está siendo examinado

08como five.wtfse estableció anteriormente 'potato', vuelve 'potato'aquí

09El fiveobjeto se multiplica por el valor primitivo 5. Esto no es diferente de cualquier otro objeto que se esté multiplicando y se explica en la sección 11.5 de la especificación ES5 . De particular interés es cómo los objetos se convierten en valores numéricos, que se cubre en algunas secciones.

9.3 ToNumber :

  1. Deje que primValue sea ToPrimitive (argumento de entrada, número de pista).
  2. Regresar a número (primValue).

9.1 ToPrimitive :

Devuelve un valor predeterminado para el objeto. El valor predeterminado de un objeto se recupera llamando al método interno [[DefaultValue]] del objeto, pasando la sugerencia opcional PreferredType. El comportamiento del método interno [[DefaultValue]] se define en esta especificación para todos los objetos ECMAScript nativos en 8.12.8 .

8.12.8 [[Valor predeterminado]] :

Deje que valueOf sea el resultado de llamar al método interno [[Get]] del objeto O con el argumento "valueOf".

  1. Si IsCallable (valueOf) es verdadero, entonces,

    1. Sea val el resultado de llamar al método interno [value] de valueOf, con O como este valor y una lista de argumentos vacía.
    2. Si val es un valor primitivo, devuelve val.

Esta es una forma indirecta de decir que valueOfse llama a la función del objeto y que el valor de retorno de esa función se usa en la ecuación. Si tuviera que cambiar la valueOffunción, podría cambiar los resultados de la operación:

> five.valueOf = function () { return 10 }
undefined
> five * 5
50

10como fivela valueOffunción s no se modificó, devuelve el valor primitivo envuelto 5para que five * 5evalúe a 5 * 5qué resultados25

11la propiedad fivedel objeto wtfse evalúa nuevamente a pesar de no haberse modificado desde el momento en que se asignó 05.

12 'potato'

13el Incremento del operador Postfix se llama el fiveque obtiene el valor numérico ( 5, cubrimos la forma anteriormente), almacena el valor para que pueda ser devuelto, se suma 1al valor ( 6), asigna el valor a five, y devuelve el valor almacenado ( 5)

14 como antes, el valor devuelto es el valor antes de que se incrementara

15Se accede a la wtfpropiedad del valor primitivo ( 6) almacenado en la variable five. La sección 15.7.5 de la especificación ES5 define este comportamiento. Los números obtienen las propiedades de Number.prototype.

16 Number.prototypeno tiene una wtfpropiedad, por lo que undefinedse devuelve

17 five.wtfse le asigna un valor de 'potato?'. La asignación se define en 11.13.1 de la especificación ES5 . Básicamente, el valor asignado se devuelve pero no se almacena.

18 'potato?' fue devuelto por el operador de asignación

19nuevamente five, que tiene un valor de 6se accede, y nuevamente Number.prototypeno tiene una wtfpropiedad

20 undefined como se explicó anteriormente

21 five se accede

22 6 se devuelve como se explica en 13

zzzzBov
fuente
17

Es muy simple

function dis () { return this; }

Esto devuelve el thiscontexto. Entonces, si lo haces call(5), estás pasando el número como un objeto.

La callfunción no proporciona argumentos, el primer argumento que da es el contexto de this. Por lo general, si lo desea en su contexto, se lo da, {}lo dis.call({})que significa que thisen la función está vacío this. Sin embargo, si pasa 5, parece que se convertirá en un objeto. Ver .call

Entonces el regreso es object

Cuando lo hace five * 5, JavaScript ve el objeto fivecomo el tipo primitivo, por lo que es equivalente a 5 * 5. Curiosamente, hazlo '5' * 5, todavía es igual 25, por lo que JavaScript está claramente bajo el capó. No fivese realizan cambios en el tipo subyacente en esta línea

Pero cuando lo haga ++, convertirá el objeto al numbertipo primitivo , eliminando así la .wtfpropiedad. Porque estás afectando el tipo subyacente

Callum Linington
fuente
La devolución es número .
GSerg
++convierte y lo asigna de nuevo. ++es igual a variable = variable + 1. Así que pierdeswtf
Rajesh
El *vs ++no me confunde en absoluto. Tener una función que no espera argumentos y retorna this, tener esa función acepta un argumento de todos modos y devuelve algo que es, pero no exactamente, el argumento, eso no tiene sentido para mí.
Nathan Long
Lo actualizó @NathanLong para mostrar lo que dis.call()hace.
Callum Linington
5 ++ se comporta igual que en lenguaje C, por lo que no es nada sorprendente. thises simplemente un puntero a un objeto, por lo que los tipos primitivos se convierten implícitamente. ¿Por qué una función sin argumentos no podría tener al menos un contexto? El primer argumento de callo bindse utiliza para establecer el contexto. Además, las funciones son cierres, lo que significa que tienen acceso a más que soloarguments
Rivenfall, el
10

Los valores primitivos no pueden tener propiedad. Pero cuando intentas acceder a una propiedad con valor primitivo, se transcribe de forma transparente a un objeto numérico temporal.

Entonces:

> function dis() { return this }
undefined
// Like five.dis(), so dis return the temporaty Number object and 
// reference it in five
> five = dis.call(5)
Number {[[PrimitiveValue]]: 5}

// Write the wtf attribut on the Number object referenced by five
> five.wtf = 'potato'
"potato"
// Read the wtf attribut on the Number object referenced by five
> five.wtf
"potato"

// Return 5*5 but dont change the reference of five
> five * 5
25
// Read the same wtf attribut on the Number object referenced by five
> five.wtf
"potato"

// Change the five reference to a new primitive value (5+1). Five
// reference a primitive now.
> five++
5

// Read the wtf attribut on a new temporary Number object construct from
// the primitive referenced by five. So wtf does not exist.
> five.wtf
undefined

// Write the wtf attribut on a new temporary Number object construct from
// the primitive referenced by five. But this object not referenced by
// five. It will be lost.
> five.wtf = 'potato?'
"potato?"

// Read the wtf attribut on a new temporary Number object construct from
// the primitive referenced by five. So wtf does not exist.
> five.wtf
undefined
> five
6
Techniv
fuente
8

Declarar función dis. La función devuelve su contexto

function dis() { return this }
undefined

Llamar discon contexto 5. Los valores primitivos se encuadran cuando se pasan como contexto en modo estricto ( MDN ). Entonces fiveahora es objeto (número en caja).

five = dis.call(5)
Number {[[PrimitiveValue]]: 5}

Declarar wtfpropiedad en fivevariable

five.wtf = 'potato'
"potato"

Valor de five.wtf

five.wtf
"potato"

fiveestá encuadrado 5, por lo que es número y objeto al mismo tiempo (5 * 5 = 25). No cambia five.

five * 5
25

Valor de five.wtf

five.wtf
"potato"

Unboxing fiveaquí. fiveahora es simplemente primitivo number. Se imprime 5y luego se agrega 1a five.

five++
5

fivees un número primitivo 6ahora, no tiene propiedades.

five.wtf
undefined

las primitivas no pueden tener propiedades, no puede establecer esto

five.wtf = 'potato?'
"potato?"

no puedes leer esto, porque no estaba configurado

five.wtf
undefined

fivese 6debe a que la publicación se incrementó arriba

five
6
Maxx
fuente
7

En primer lugar, parece que esto se está ejecutando a través de la consola de nodejs.

1)

    function dis() { return this }

crea la función dis (), pero debido a que no se configuró como varno había ningún valor que devolver, también lo undefinedfue la salida, aunque dis()se definió. En una nota al margen, thisno se devolvió porque la función no se ejecutó.

2)

    five = dis.call(5)

Esto devuelve Javascript de Numberobjeto porque se acaba de establecer la función de dis()'s thisvalor a la primitiva cinco.

3)

   five.wtf = 'potato'

El primero vuelve "potato"porque acabas de establecer la propiedad wtfde fiveto 'potato'. Javascript devuelve el valor de una variable se establece, por lo que es fácil de múltiples variables de la cadena y los puso en el mismo valor como esto: a = b = c = 2.

4)

    five * 5

Esto vuelve 25porque acabas de multiplicar el número primitivo 5por five. El valor de fivefue determinado por el valor del Numberobjeto.

5)

    five.wtf

Me salteé esta línea antes porque la repetiría aquí. Simplemente devuelve el valor de la propiedad wtfque configuró anteriormente.

6)

    five++

Como dijo @Callum, ++convertirá el tipo al numbermismo valor del objeto Number {[[PrimitiveValue]]: 5}}.

Ahora, como fivees un number, ya no puede establecer propiedades hasta que haga algo como esto:

    five = dis.call(five)
    five.wtf = "potato?"

o

    five = { value: 6, wtf: "potato?" }

También tenga en cuenta que la segunda forma tendrá un comportamiento diferente al uso del primer método porque está definiendo un objeto genérico en lugar del Numberobjeto que se creó anteriormente.

Espero que esto ayude, a JavaScript le gusta asumir cosas, por lo que puede ser confuso cuando se cambia del Numberobjeto a primitivo number. Puede comprobar qué tipo es algo mediante el uso de la typeofpalabra, la escritura typeof cinco después de inicializar vuelve 'object', y después de hacerlo five++se vuelve 'number'.

@deceze describe la diferencia entre el objeto Number y el número primitivo extremadamente bien.

Patrick Barr
fuente
6

Los ámbitos de JavaScript están hechos de contextos de ejecución. Cada contexto de ejecución tiene un entorno léxico (valores con ámbito externo / global), un entorno variable (valores con ámbito local) y este enlace .

La presente unión es una parte muy importante del contexto de ejecución. Usar calles una forma de alterar este enlace , y al hacerlo creará automáticamente un objeto para llenar el enlace.

Function.prototype.call () (de MDN)

Sintaxis
fun.call(thisArg[, arg1[, arg2[, ...]]])

thisArg
El valor de esto proporcionó la llamada a la diversión. Tenga en cuenta que este puede no ser el valor real visto por el método: si el método es una función en código de modo no estricto, nulo e indefinido se reemplazará con el objeto global y los valores primitivos se convertirán en objetos . (énfasis mío)

Una vez que es evidente que 5 se está convirtiendo new Number(5), el resto debería ser bastante obvio. Tenga en cuenta que otros ejemplos también funcionarán siempre que sean valores primitivos.

function primitiveToObject(prim){
  return dis.call(prim);
}
function dis(){ return this; }

//existing example
console.log(primitiveToObject(5));

//Infinity
console.log(primitiveToObject(1/0));

//bool
console.log(primitiveToObject(1>0));

//string
console.log(primitiveToObject("hello world"));
<img src="http://i.stack.imgur.com/MUyRV.png" />

ingrese la descripción de la imagen aquí

Travis J
fuente
4

Un par de conceptos explican lo que sucede.

5 es un número, un valor primitivo

Number {[[PrimitiveValue]]: 5} es una instancia de Number (llamémosla envoltura de objeto)

Siempre que acceda a una propiedad / método en un valor primitivo, el motor JS creará un contenedor de objetos del tipo apropiado ( Numberpara 5, Stringpara 'str'y Booleanpara true) y resolverá la llamada de acceso a propiedades / método en ese contenedor de objetos. Esto es lo que sucede cuando lo haces, true.toString()por ejemplo.

Al realizar operaciones en objetos, se convierten en valores primitivos (usando toStringo valueOf) para resolver esas operaciones, por ejemplo, al hacer

var obj = { a : 1 };
var string = 'mystr' + obj;
var number = 3 + obj;

stringmantendrá la concatenación de cadenas de mystry obj.toString()y numbermantendrá la adición de 3y obj.valueOf().

Ahora para ponerlo todo junto

five = dis.call(5)

dis.call(5)se comporta como (5).dis()si 5realmente tuviera el método dis. Para resolver la llamada al método, se crea el contenedor de objetos y la llamada al método se resuelve en él. En este punto, cinco puntos a un contenedor de objetos alrededor del valor primitivo 5.

five.wtf = 'potato'

Establecer una propiedad en un objeto, nada lujoso aquí.

five * 5

En realidad, esto es five.valueOf() * 5obtener el valor primitivo del contenedor de objetos. fiveTodavía apunta al objeto inicial.

five++

Esto es en realidad five = five.valueOf() + 1. Antes de que esta línea fivecontenga el objeto envoltorio alrededor del valor 5, mientras que después de este punto se fivemantiene el valor primitivo6 .

five.wtf
five.wtf = 'potato?'
five.wtf

fiveya no es un objeto. Cada una de esas líneas crea una nueva instancia de Número para resolver el .wtfacceso a la propiedad. Las instancias son independientes, por lo que establecer una propiedad en una no será visible en otra. El código es completamente equivalente a este:

(new Number(6)).wtf;
(new Number(6)).wtf = 'potato?';
(new Number(6)).wtf;
Tibos
fuente