¿Cómo funciona la asignación de variables en JavaScript?

97

Así que estuve jugando el otro día solo para ver exactamente cómo funciona la asignación masiva en JavaScript.

Primero probé este ejemplo en la consola:

a = b = {};
a.foo = 'bar';
console.log(b.foo);

El resultado fue que se mostraba una "barra" en una alerta. Eso es bastante justo, ay en brealidad son solo alias para el mismo objeto. Luego pensé, ¿cómo podría simplificar este ejemplo?

a = b = 'foo';
a = 'bar';
console.log(b);

Eso es más o menos lo mismo, ¿no? Bueno, esta vez, devuelve foono barlo que cabe esperar del comportamiento del primer ejemplo.

¿Por qué pasó esto?

NB Este ejemplo podría simplificarse aún más con el siguiente código:

a = {};
b = a;
a.foo = 'bar';
console.log(b.foo);

a = 'foo';
b = a;
a = 'bar';
console.log(b);

(Sospecho que JavaScript trata las primitivas como cadenas y enteros de manera diferente a los hash. Los hash devuelven un puntero mientras que las primitivas "centrales" devuelven una copia de sí mismas)

Chris Lloyd
fuente
Explicando la asignación aquí: syntaxsuccess.com/viewarticle/…
TGH

Respuestas:

114

En el primer ejemplo, está configurando una propiedad de un objeto existente. En el segundo ejemplo, está asignando un objeto nuevo.

a = b = {};

ay bahora son punteros al mismo objeto. Entonces, cuando lo hagas:

a.foo = 'bar';

También establece b.foodesde ay bapunta al mismo objeto.

¡Sin embargo!

Si haces esto en su lugar:

a = 'bar';

estás diciendo que ahora aapunta a un objeto diferente. Esto no tiene ningún efecto sobre lo aseñalado antes.

En JavaScript, asignar una variable y asignar una propiedad son 2 operaciones diferentes. Es mejor pensar en las variables como punteros a objetos, y cuando asigna directamente a una variable, no está modificando ningún objeto, simplemente re-apuntando su variable a un objeto diferente.

Pero asignar una propiedad, como a.foo, modificará el objeto al que aapunta. Esto, por supuesto, también modifica todas las demás referencias que apuntan a este objeto simplemente porque todas apuntan al mismo objeto.

Alex Wayne
fuente
3
"estás diciendo que a apunta a un objeto diferente ahora". No, no uses la palabra objeto. Una cadena no es un objeto en JavaScript.
Nosredna
9
Tal vez las cadenas no sean técnicamente del tipo "Objeto" de javascript, pero se pueden pensar en objetos en el sentido de OO.
Alex Wayne
2
@Squeegy: las cadenas son primitivas, no objetos: ¡no puede asignar propiedades arbitrarias a las cadenas! Solo se comportan como objetos debido a lo que se llama autoboxing en Java
Christoph
11
Pero las cadenas tienen métodos y propiedades, y el prototipo de String definitivamente se puede modificar. Seguro que actúan como objetos.
Cameron Martin
9
@ddlshack: Como explicó Christoph, esto se debe al autoboxing. Si tuviera que definir una cadena a través de var foo = new String('foo');, entonces sería un objeto de cadena (y lo confirmará). Pero si lo declara a través de un literal de cadena, entonces son primitivas de cadena. Ver: developer.mozilla.org/en/JavaScript/Reference/Global_Objects/…typeof
Lèse majesté
26

Su pregunta ya ha sido respondida satisfactoriamente por Squeegy: no tiene nada que ver con objetos frente a primitivas, sino con reasignación de variables frente a establecer propiedades en el mismo objeto referenciado.

Parece haber mucha confusión sobre los tipos de JavaScript en las respuestas y comentarios, así que aquí hay una pequeña introducción al sistema de tipos de JavaScript:

En JavaScript, hay dos tipos de valores fundamentalmente diferentes: primitivos y objetos (y no hay nada como un 'hash').

Las cadenas, los números y los valores booleanos, así como nully undefinedson primitivos, los objetos son todo lo que puede tener propiedades. Incluso las matrices y las funciones son objetos regulares y, por lo tanto, pueden contener propiedades arbitrarias. Solo difieren en la propiedad interna [[Clase]] (las funciones además tienen una propiedad llamada [[Llamar]] y [[Construir]], pero bueno, eso es detalles).

La razón por la que los valores primitivos pueden comportarse como objetos se debe al autoencuadre, pero los primitivos en sí mismos no pueden tener ninguna propiedad.

Aquí hay un ejemplo:

var a = 'quux';
a.foo = 'bar';
document.writeln(a.foo);

Esto dará como resultado undefined: acontiene un valor primitivo, que se promueve a un objeto cuando se asigna la propiedad foo. Pero este nuevo objeto se descarta inmediatamente, por lo que foose pierde el valor de .

Piensa en esto, de esta manera:

var a = 'quux';
new String(a).foo = 'bar'; // we never save this new object anywhere!
document.writeln(new String(a).foo); // a completly new object gets created
Christoph
fuente
La página de la Fundación Mozilla 'Una reintroducción a JavaScript (tutorial de JS)' describe los objetos JavaScript "como simples colecciones de pares nombre-valor. Como tales, son similares a ...", luego sigue con una lista de diccionarios, hash , tablas hash y mapas hash de varios lenguajes de programación. La misma página describe las referencias a las propiedades del objeto como búsquedas de tablas hash. Entonces, los objetos son todo como una tabla 'hash'. Esto no anula la otra información útil, pero la caracterización original de Chris Lloyd no era inexacta.
C Perkins
2

Estás más o menos en lo correcto excepto que lo que te refieres como un "hash" es en realidad una sintaxis abreviada de un objeto.

En el primer ejemplo, un y b ambos se refieren al mismo objeto. En el segundo ejemplo, cambia a para hacer referencia a otra cosa.

Kevin
fuente
Entonces, ¿por qué el doble rasero para Object?
Chris Lloyd
No es un doble rasero. En el primer ejemplo, ayb todavía se refieren al mismo objeto, solo está modificando una propiedad de ese objeto. En el segundo ejemplo, estás apuntando a un objeto diferente.
Kevin
1
No, la diferencia es que en el segundo caso, se trata de una cadena, no de un objeto.
Nosredna
1
Para ser claros: esto no tiene nada que ver con cadenas que devuelven una copia de sí mismas. La razón por la que los dos fragmentos de código son diferentes está en el segundo párrafo de Kevin (explicado con más detalle en la respuesta de Squeegy).
Chuck
No importa si tiene una cadena o un objeto en la variable. Usted asigna un valor nuevo y diferente y luego la variable contiene ese valor nuevo y diferente.
sth
2

aquí está mi versión de la respuesta:

obj = {a:"hello",b:"goodbye"}
x = obj
x.a = "bonjour"

// now obj.a is equal to "bonjour"
// because x has the same reference in memory as obj
// but if I write:
x = {}
x.a = obj.a
x.b = obj.b
x.a = "bonjour"

// now x = {a:"bonjour", b:"goodbye"} and obj = {a:"hello", b:"goodbye"}
// because x points to another place in the memory
Bashir Abdelwahed
fuente
0

Está configurando a para que apunte a un nuevo objeto de cadena, mientras que b sigue apuntando al antiguo objeto de cadena.

mdm
fuente
0

En el primer caso se cambia alguna propiedad del objeto contenido en la variable, en el segundo caso se asigna un nuevo valor a la variable. Eso son cosas fundamentalmente diferentes. Las variables ay bno están vinculadas mágicamente de alguna manera por la primera asignación, solo contienen el mismo objeto. Ese también es el caso en el segundo ejemplo, hasta que asigne un nuevo valor a la bvariable.

algo
fuente
0

La diferencia está entre tipos y objetos simples.

Todo lo que sea un objeto (como una matriz o una función) se pasa por referencia.

Todo lo que sea de un tipo simple (como una cadena o un número) se copia.

Siempre tengo una función copyArray a mano para poder estar seguro de que no estoy creando un montón de alias para la misma matriz.

Nosredna
fuente
La diferencia no se nota en muchos escenarios, pero Javascript no pasa ni asigna por referencia. Copia valores de referencia.
Juan Pablo Califano
Estos chicos ya han hecho un buen trabajo explicándolo, así que pegaré el enlace: stackoverflow.com/questions/40480/is-java-pass-by-reference (me refiero a Java, pero la semántica para pasar y asignar los valores / referencias son los mismos que en Javascript)
Juan Pablo Califano
1
En realidad, esta respuesta es incorrecta, todo se pasa por valor en JavaScript. De MDN, "Los parámetros de una llamada de función son los argumentos de la función. Los argumentos se pasan a las funciones por valor. Si la función cambia el valor de un argumento, este cambio no se refleja globalmente o en la función que llama. Sin embargo, las referencias a objetos son valores también, y son especiales: si la función cambia las propiedades del objeto referido, ese cambio es visible fuera de la función ". developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
bittersweetryan
Los primitivos se comportan como objetos inmutables ( exactamente como ellos en modo estricto). Esta respuesta no es correcta.
Ry-