Jak działa przypisywanie zmiennych w JavaScript?


98

Pewnego dnia bawiłem się więc tylko po to, aby zobaczyć dokładnie, jak działa przypisywanie masowe w JavaScript.

Najpierw wypróbowałem ten przykład w konsoli:

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

Rezultatem było wyświetlenie „słupka” w alercie. To jest w porządku ai btak naprawdę to tylko aliasy tego samego obiektu. Wtedy pomyślałem, jak mógłbym uprościć ten przykład.

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

To prawie to samo, prawda? Cóż, tym razem nie wraca footak, barjak bym się spodziewał po zachowaniu pierwszego przykładu.

Dlaczego to się dzieje?

Uwaga Ten przykład można jeszcze bardziej uprościć za pomocą następującego kodu:

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

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

(Podejrzewam, że JavaScript traktuje prymitywy, takie jak łańcuchy i liczby całkowite, inaczej niż skróty. Hashe zwracają wskaźnik, podczas gdy "podstawowe" prymitywy zwracają swoją kopię)


Zadanie wyjaśniające tutaj: syntaxsuccess.com/viewarticle/…
TGH

Odpowiedzi:


114

W pierwszym przykładzie ustawiasz właściwość istniejącego obiektu. W drugim przykładzie przypisujesz zupełnie nowy obiekt.

a = b = {};

ai bsą teraz wskaźnikami do tego samego obiektu. Więc kiedy to zrobisz:

a.foo = 'bar';

Ustawia b.foo również od tego samego obiektu ai bwskazuje na ten sam obiekt.

Jednak!

Jeśli zamiast tego zrobisz to:

a = 'bar';

ty to mówisz a wskazuje teraz na inny obiekt. Nie ma to wpływu na to, o czym amówiliśmy wcześniej.

W JavaScript przypisywanie zmiennej i przypisywanie właściwości to dwie różne operacje. Najlepiej jest myśleć o zmiennych jako o wskaźnikach do obiektów, a kiedy przypisujesz je bezpośrednio do zmiennej, nie modyfikujesz żadnych obiektów, a jedynie przenosisz zmienną na inny obiekt.

Ale przypisanie właściwości, na przykład a.foo, zmodyfikuje obiekt, na który awskazuje. To oczywiście modyfikuje również wszystkie inne odniesienia, które wskazują na ten obiekt, po prostu dlatego, że wszystkie wskazują na ten sam obiekt.


3
"mówisz, że teraz wskazuje inny obiekt." Nie, nie używaj słowa obiekt. Ciąg nie jest obiektem w JavaScript.
Nosredna

9
Może z technicznego punktu widzenia łańcuchy znaków nie są typu javascript „Object”, ale można je traktować jako obiekty w sensie OO.
Alex Wayne

2
@Squeegy: łańcuchy są prymitywami, a nie obiektami: nie można przypisać dowolnych właściwości do łańcuchów! Zachowują się jak obiekt tylko z powodu tego, co w Javie nazywa się autoboxing
Christoph,

11
Ale łańcuchy mają metody i właściwości, a prototyp String z pewnością można zmodyfikować. Z pewnością zachowują się jak przedmioty.
Cameron Martin

9
@ddlshack: Jak wyjaśnił Christoph, jest to spowodowane autoboxingiem. Jeśli było zdefiniować poprzez łańcuch var foo = new String('foo');, a następnie , że byłoby to obiekt string (i typeofbędzie to potwierdzić). Ale jeśli zadeklarujesz to za pomocą literału ciągu, to są to prymitywy łańcuchowe. Zobacz: developer.mozilla.org/en/JavaScript/Reference/Global_Objects/…
Lèse majesté

26

Na Twoje pytanie Squeegy odpowiedział już zadowalająco - nie ma to nic wspólnego z obiektami i prymitywami, ale z ponownym przypisaniem zmiennych a ustawieniem właściwości w tym samym obiekcie, do którego się odwołuje.

Wydaje się, że w odpowiedziach i komentarzach jest dużo niejasności co do typów JavaScript, więc oto małe wprowadzenie do systemu typów JavaScript:

W JavaScript istnieją dwa zasadniczo różne rodzaje wartości: prymitywy i obiekty (i nie ma czegoś takiego jak „hash”).

Ciągi, liczby i wartości logiczne, a także nulliundefined są prymitywami, obiekty są wszystkim, co może mieć właściwości. Nawet tablice i funkcje są zwykłymi obiektami i dlatego mogą mieć dowolne właściwości. Różnią się po prostu wewnętrzną właściwością [[Class]] (funkcje dodatkowo mają własność o nazwie [[Call]] i [[Construct]], ale cóż, to szczegóły).

Powodem, dla którego wartości pierwotne mogą zachowywać się jak obiekty, jest autoboxing, ale same prymitywy nie mogą mieć żadnych właściwości.

Oto przykład:

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

Spowoduje to undefined: aprzechowuje pierwotną wartość, która jest promowana do obiektu podczas przypisywania właściwości foo. Ale ten nowy obiekt jest natychmiast odrzucany, więc wartośćfoo jest tracona.

Pomyśl o tym w ten sposób:

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

Strona Mozilla Foundation „Ponowne wprowadzenie do JavaScript (samouczek JS)” opisuje obiekty JavaScript „jako proste zbiory par nazwa-wartość. W związku z tym są one podobne do…”, po czym następuje lista słowników, hash , tablice skrótów i mapy skrótów z różnych języków programowania. Na tej samej stronie opisano odniesienia do właściwości obiektów jako wyszukiwania w tablicy skrótów. Tak więc obiekty są wszystkim jak tablica „haszująca”. Nie unieważnia to innych przydatnych informacji, ale oryginalna charakterystyka Chrisa Lloyda nie była niedokładna.
C Perkins,

2

Jesteś mniej więcej poprawny, z wyjątkiem tego, że to, co nazywasz „hashem”, jest w rzeczywistości skróconą składnią obiektu.

W pierwszym przykładzie a i b odnoszą się do tego samego obiektu. W drugim przykładzie zmieniasz a, aby odnosić się do czegoś innego.


Skąd więc podwójne standardy Object?
Chris Lloyd,

To nie jest podwójny standard. W pierwszym przykładzie a i b nadal odnoszą się do tego samego obiektu, po prostu modyfikujesz właściwość tego obiektu. W drugim przykładzie wskazujesz na inny obiekt.
Kevin

1
Nie, różnica polega na tym, że w drugim przypadku masz do czynienia ze stringiem, a nie obiektem.
Nosredna

1
Żeby było jasne: nie ma to nic wspólnego z ciągami znaków zwracającymi swoją kopię. Powód, dla którego dwa fragmenty kodu są różne, znajduje się w drugim akapicie Kevina (dokładniej wyjaśnionym w odpowiedzi Squeegy'ego).
Chuck

Nie ma znaczenia, czy w zmiennej znajduje się ciąg, czy obiekt. Przypisujesz nową, inną wartość, a następnie zmienna zawiera tę nową, inną wartość.
sth

2

oto moja wersja odpowiedzi:

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

Ustawiasz a, aby wskazywał na nowy obiekt łańcuchowy, podczas gdy b nadal wskazuje na stary obiekt string.


0

W pierwszym przypadku zmieniasz jakąś właściwość obiektu zawartego w zmiennej, w drugim przypisujesz zmiennej nową wartość. To są zasadniczo różne rzeczy. Zmienne ai bnie są w jakiś sposób magicznie połączone przez pierwsze przypisanie, po prostu zawierają ten sam obiekt. Dzieje się tak również w drugim przykładzie, dopóki nie przypiszesz nowej wartości bzmiennej.


0

Różnica dotyczy prostych typów i obiektów.

Wszystko, co jest obiektem (np. Tablica lub funkcja), jest przekazywane przez odniesienie.

Kopiowane jest wszystko, co jest prostym typem (np. Ciąg znaków lub liczba).

Zawsze mam pod ręką funkcję copyArray, więc mogę mieć pewność, że nie tworzę wielu aliasów do tej samej tablicy.


Różnica nie jest zauważalna w wielu scenariuszach, ale JavaScript w rzeczywistości nie jest przekazywany ani przypisywany przez odniesienie. Kopiuje wartości referencyjne.
Juan Pablo Califano

Ci faceci wykonali już dobrą robotę wyjaśniając to, więc po prostu wkleję link: stackoverflow.com/questions/40480/is-java-pass-by-reference (odnoszę się do Javy, ale semantyka przekazywania i przypisywania wartości / odniesienia są takie same jak w Javascript)
Juan Pablo Califano

1
Właściwie ta odpowiedź jest nieprawidłowa, wszystko jest przekazywane przez wartość w JavaScript. Z MDN: „Parametry wywołania funkcji są argumentami funkcji. Argumenty są przekazywane do funkcji według wartości. Jeśli funkcja zmienia wartość argumentu, ta zmiana nie jest odzwierciedlana globalnie ani w funkcji wywołującej. Jednak odwołania do obiektów są wartości również i są specjalne: jeśli funkcja zmienia właściwości obiektu, do którego się odnosi, zmiana ta jest widoczna poza funkcją. " developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
bittersweetryan

Prymitywy zachowują się jak niezmienne obiekty ( dokładnie tak samo, jak w trybie ścisłym). Ta odpowiedź jest nieprawidłowa.
Ry-
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.