Comment fonctionne l'attribution de variable en JavaScript?


98

Je jouais donc l'autre jour pour voir exactement comment les affectations de masse fonctionnent en JavaScript.

J'ai d'abord essayé cet exemple dans la console:

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

Le résultat était une "barre" affichée dans une alerte. C'est assez juste, aet ce ne bsont en réalité que des alias du même objet. Puis j'ai pensé, comment pourrais-je rendre cet exemple plus simple.

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

C'est à peu près la même chose, n'est-ce pas? Eh bien cette fois, il foone revient pas barcomme je l'attendrais du comportement du premier exemple.

Pourquoi cela arrive-t-il?

NB Cet exemple pourrait être encore plus simplifié avec le code suivant:

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

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

(Je soupçonne que JavaScript traite les primitives telles que les chaînes et les entiers différemment des hachages. Les hachages renvoient un pointeur tandis que les primitives "principales" renvoient une copie d'elles-mêmes)


Expliquer l'affectation ici: syntaxsuccess.com/viewarticle/…
TGH

Réponses:


114

Dans le premier exemple, vous définissez une propriété d'un objet existant. Dans le deuxième exemple, vous attribuez un tout nouvel objet.

a = b = {};

aet bsont maintenant des pointeurs vers le même objet. Alors quand vous faites:

a.foo = 'bar';

Il fixe b.fooaussi depuis aet bpointe vers le même objet.

Toutefois!

Si vous faites cela à la place:

a = 'bar';

vous dites que cela apointe vers un objet différent maintenant. Cela n'a aucun effet sur ce qui a été asignalé précédemment.

En JavaScript, l'affectation d'une variable et l'affectation d'une propriété sont 2 opérations différentes. Il est préférable de considérer les variables comme des pointeurs vers des objets, et lorsque vous affectez directement à une variable, vous ne modifiez aucun objet, vous rejoignez simplement votre variable vers un objet différent.

Mais attribuer une propriété, comme a.foo, modifiera l'objet vers lequel apointe. Ceci, bien sûr, modifie également toutes les autres références qui pointent vers cet objet simplement parce qu'elles pointent toutes vers le même objet.


3
"vous dites que a pointe vers un objet différent maintenant." Non, n'utilisez pas le mot objet. Une chaîne n'est pas un objet en JavaScript.
Nosredna

9
Peut-être que les chaînes ne sont pas techniquement du type javascript "Object", mais elles peuvent être considérées comme des objets au sens OO.
Alex Wayne

2
@Squeegy: les chaînes sont des primitives, pas des objets: vous ne pouvez pas affecter des propriétés arbitraires aux chaînes! Ils ne se comportent que comme des objets à cause de ce que l'on appelle l'autoboxing en Java
Christoph

11
Mais les chaînes ont des méthodes et des propriétés, et le prototype de String peut certainement être modifié. Ils agissent certainement comme des objets.
Cameron Martin

9
@ddlshack: Comme Christoph l'a expliqué, cela est dû à l'autoboxing. Si vous deviez définir une chaîne via var foo = new String('foo');, alors ce serait un objet chaîne (et typeofle confirmera). Mais si vous le déclarez via une chaîne littérale, alors ce sont des chaînes primitives. Voir: developer.mozilla.org/en/JavaScript/Reference/Global_Objects
...

26

Votre question a déjà reçu une réponse satisfaisante de Squeegy - cela n'a rien à voir avec les objets vs les primitives, mais avec la réaffectation des variables vs la définition des propriétés dans le même objet référencé.

Il semble y avoir beaucoup de confusion sur les types JavaScript dans les réponses et les commentaires, alors voici une petite introduction au système de type de JavaScript:

En JavaScript, il existe deux types de valeurs fondamentalement différents: les primitives et les objets (et il n'y a rien de tel qu'un «hachage»).

Chaînes, nombres et booléens ainsi que nullet undefinedsont des primitifs, les objets sont tout ce qui peut avoir des propriétés. Même les tableaux et les fonctions sont des objets réguliers et peuvent donc contenir des propriétés arbitraires. Ils diffèrent simplement par la propriété interne [[Class]] (les fonctions ont en plus une propriété appelée [[Call]] et [[Construct]], mais bon, ce sont des détails).

La raison pour laquelle les valeurs primitives peuvent se comporter comme des objets est due à l'autoboxing, mais les primitives elles-mêmes ne peuvent contenir aucune propriété.

Voici un exemple:

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

Cela produira undefined: acontient une valeur primitive, qui est promue en objet lors de l'attribution de la propriété foo. Mais ce nouvel objet est immédiatement rejeté, donc la valeur de fooest perdue.

Pensez-y comme ceci:

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

La page de la Fondation Mozilla «Une réintroduction à JavaScript (tutoriel JS)» décrit les objets JavaScript «comme de simples collections de paires nom-valeur. En tant que telles, elles sont similaires à ...», puis suit avec une liste de dictionnaires, hachage , tables de hachage et cartes de hachage de divers langages de programmation. La même page décrit les références de propriété d'objet en tant que recherches de table de hachage. Les objets sont donc tout comme une table de «hachage». Cela n'annule pas les autres informations utiles, mais la caractérisation originale de Chris Lloyd n'était pas inexacte.
C Perkins

2

Vous avez plus ou moins raison, sauf que ce que vous appelez un "hachage" n'est en fait qu'une syntaxe abrégée pour un objet.

Dans le premier exemple, a et b font tous deux référence au même objet. Dans le deuxième exemple, vous modifiez a pour faire référence à autre chose.


Alors pourquoi le double standard pour Object?
Chris Lloyd

Ce n'est pas un double standard. Dans le premier exemple, a et b font toujours référence au même objet, vous modifiez simplement une propriété de cet objet. Dans le deuxième exemple, vous pointez un vers un objet différent.
Kevin le

1
Non, la différence est que dans le second cas, vous avez affaire à une chaîne, pas à un objet.
Nosredna le

1
Pour être clair: cela n'a rien à voir avec les chaînes retournant une copie d'elles-mêmes. La raison pour laquelle les deux extraits de code sont différents se trouve dans le deuxième paragraphe de Kevin (expliqué plus en détail dans la réponse de Squeegy).
Chuck le

Peu importe que vous ayez une chaîne ou un objet dans la variable. Vous affectez une nouvelle valeur différente, puis la variable contient cette nouvelle valeur différente.
STH

2

voici ma version de la réponse:

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

0

Vous définissez a pour qu'il pointe vers un nouvel objet chaîne, tandis que b continue de pointer vers l'ancien objet chaîne.


0

Dans le premier cas, vous modifiez une propriété de l'objet contenu dans la variable, dans le second cas vous affectez une nouvelle valeur à la variable. Ce sont des choses fondamentalement différentes. Les variables aet bne sont pas en quelque sorte liées par magie par la première affectation, elles contiennent juste le même objet. C'est également le cas dans le deuxième exemple, jusqu'à ce que vous affectiez une nouvelle valeur à la bvariable.


0

La différence se situe entre les types simples et les objets.

Tout ce qui est un objet (comme un tableau ou une fonction) est passé par référence.

Tout ce qui est de type simple (comme une chaîne ou un nombre) est copié.

J'ai toujours une fonction copyArray à portée de main pour être sûr de ne pas créer un tas d'alias vers le même tableau.


La différence n'est pas perceptible dans de nombreux scénarios, mais Javascript ne passe pas ou n'attribue pas réellement par référence. Il copie les valeurs de référence.
Juan Pablo Califano le

Ces gars ont déjà fait un bon travail pour l'expliquer, alors je vais simplement coller le lien: stackoverflow.com/questions/40480/is-java-pass-by-reference (je fais référence à Java, mais la sémantique pour passer et assigner les valeurs / références sont les mêmes qu'en Javascript)
Juan Pablo Califano

1
En fait cette réponse est incorrecte, tout est passé par valeur en JavaScript. De MDN, "Les paramètres d'un appel de fonction sont les arguments de la fonction. Les arguments sont passés aux fonctions par valeur. Si la fonction change la valeur d'un argument, cette modification n'est pas reflétée globalement ou dans la fonction appelante. Cependant, les références d'objet sont valeurs, aussi, et elles sont spéciales: si la fonction change les propriétés de l'objet référencé, ce changement est visible en dehors de la fonction. " developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
bittersweetryan

Les primitives se comportent comme des objets immuables ( exactement comme eux en mode strict). Cette réponse n'est pas correcte.
Ry-
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.