Czy JavaScript jest językiem przekazującym lub przekazującym wartość?
Typy pierwotne (liczba, ciąg znaków itp.) Są przekazywane przez wartość, ale obiekty są nieznane, ponieważ oba mogą być przekazywane przez wartość (w przypadku, gdy uznamy, że zmienna przechowująca obiekt jest w rzeczywistości odniesieniem do obiektu ) i przekazane przez referencję (jeśli weźmiemy pod uwagę, że zmienna obiektu zawiera sam obiekt).
Chociaż na końcu nie ma to większego znaczenia, chcę wiedzieć, jaki jest właściwy sposób przedstawienia argumentów zgodnych z konwencjami. Czy istnieje fragment specyfikacji JavaScript, który określa, jaka powinna być semantyka w tym zakresie?
Odpowiedzi
Jest to interesujące w JavaScript. Rozważmy ten przykład:
function changeStuff(a, b, c)
{
a = a * 10;
b.item = "changed";
c = {item: "changed"};
}
var num = 10;
var obj1 = {item: "unchanged"};
var obj2 = {item: "unchanged"};
changeStuff(num, obj1, obj2);
console.log(num);
console.log(obj1.item);
console.log(obj2.item);
To daje wynik:
10
changed
unchanged
- Gdyby w
obj1
ogóle nie było odniesieniem, to zmianaobj1.item
nie miałaby wpływu naobj1
zewnętrzną stronę funkcji. - Gdyby argument był właściwym odniesieniem, wszystko by się zmieniło.
num
byłby100
iobj2.item
czytał"changed"
.
Zamiast tego sytuacja jest taka, że przekazywany element jest przekazywany według wartości. Ale element, który jest przekazywany przez wartość, sam jest odwołaniem. Z technicznego punktu widzenia nazywa się to udostępnianiem połączeń .
W praktyce oznacza to, że jeśli zmienisz sam parametr (jak w przypadku num
i obj2
), nie wpłynie to na element, który został wprowadzony do parametru. Ale jeśli zmienisz INTERNALS parametru, nastąpi propagacja kopii zapasowej (tak jak w przypadku obj1
).
Jest zawsze przekazywany przez wartość, ale dla obiektów wartość zmiennej jest odniesieniem. Z tego powodu, gdy przekazujesz obiekt i zmieniasz jego składowe , te zmiany pozostają poza funkcją. To sprawia, że wygląda jak przekazywanie przez odniesienie. Ale jeśli faktycznie zmienisz wartość zmiennej obiektu, zobaczysz, że zmiana nie jest trwała, udowadniając, że naprawdę jest przekazywana przez wartość.
Przykład:
function changeObject(x) {
x = { member: "bar" };
console.log("in changeObject: " + x.member);
}
function changeMember(x) {
x.member = "bar";
console.log("in changeMember: " + x.member);
}
var x = { member: "foo" };
console.log("before changeObject: " + x.member);
changeObject(x);
console.log("after changeObject: " + x.member); /* change did not persist */
console.log("before changeMember: " + x.member);
changeMember(x);
console.log("after changeMember: " + x.member); /* change persists */
Wynik:
before changeObject: foo
in changeObject: bar
after changeObject: foo
before changeMember: foo
in changeMember: bar
after changeMember: bar
Zmienna nie "trzyma" obiektu; zawiera odniesienie. Możesz przypisać to odniesienie do innej zmiennej, a teraz oba odnoszą się do tego samego obiektu. Jest zawsze przekazywany przez wartość (nawet jeśli ta wartość jest odniesieniem ...).
Nie ma możliwości zmiany wartości przechowywanej przez zmienną przekazaną jako parametr, co byłoby możliwe, gdyby JavaScript obsługiwał przekazywanie przez referencję.
Moje dwa centy ... Tak to rozumiem. (Możesz mnie poprawić, jeśli się mylę)
Nadszedł czas, aby wyrzucić wszystko, co wiesz o przekazaniu przez wartość / odniesienie.
Ponieważ w JavaScript nie ma znaczenia, czy jest przekazywana przez wartość, czy przez referencję, czy cokolwiek innego. Liczy się mutacja a przypisanie parametrów przekazywanych do funkcji.
OK, pozwól, że zrobię co w mojej mocy, aby wyjaśnić, co mam na myśli. Powiedzmy, że masz kilka obiektów.
var object1 = {};
var object2 = {};
To, co zrobiliśmy, to „przypisanie” ... Do zmiennych „object1” i „object2” przypisaliśmy 2 oddzielne puste obiekty.
Powiedzmy teraz, że bardziej lubimy obiekt1 ... Więc „przypisujemy” nową zmienną.
var favoriteObject = object1;
Następnie z jakiegoś powodu stwierdzamy, że obiekt 2 bardziej nam się podoba. Więc dokonujemy małej zmiany przydziału.
favoriteObject = object2;
Nic się nie stało z obiektem object1 ani object2. W ogóle nie zmieniliśmy żadnych danych. Wszystko, co zrobiliśmy, to ponowne przypisanie naszego ulubionego obiektu. Ważne jest, aby wiedzieć, że object2 i favouriteObject są przypisane do tego samego obiektu. Możemy zmienić ten obiekt za pomocą jednej z tych zmiennych.
object2.name = 'Fred';
console.log(favoriteObject.name) // Logs Fred
favoriteObject.name = 'Joe';
console.log(object2.name); // Logs Joe
OK, spójrzmy teraz na prymitywy, takie jak na przykład łańcuchy
var string1 = 'Hello world';
var string2 = 'Goodbye world';
Ponownie wybieramy ulubioną.
var favoriteString = string1;
Obie nasze zmienne favouriteString i string1 są przypisane do „Hello world”. A co jeśli chcemy zmienić nasz ulubiony ciąg ??? Co się stanie???
favoriteString = 'Hello everyone';
console.log(favoriteString); // Logs 'Hello everyone'
console.log(string1); // Logs 'Hello world'
Ups ... Co się stało. Nie mogliśmy zmienić string1, zmieniając favouriteString ... Dlaczego ?? Ponieważ nie zmieniliśmy naszego obiektu string . Wszystko, co zrobiliśmy, to „RE ASSIGN”, zmienną favouriteString na nowy ciąg. To zasadniczo odłączyło go od string1. W poprzednim przykładzie, kiedy zmieniliśmy nazwę naszego obiektu, niczego nie przypisaliśmy. (No cóż, nie samej zmiennej , ... jednak przypisaliśmy właściwość name do nowego ciągu znaków.) Zamiast tego zmutowaliśmy obiekt, który utrzymuje połączenia między dwiema zmiennymi a obiektami bazowymi. (Nawet gdybyśmy chcieli zmodyfikować lub zmutować sam obiekt ciągu znaków , nie moglibyśmy tego zrobić, ponieważ łańcuchy znaków są w rzeczywistości niezmienne w JavaScript).
A teraz do funkcji i przekazywania parametrów ... Kiedy wywołujesz funkcję i przekazujesz parametr, zasadniczo robisz "przypisanie" do nowej zmiennej i działa to dokładnie tak samo, jak gdybyś przypisywał ją za pomocą znak równości (=).
Weź te przykłady.
var myString = 'hello';
// Assign to a new variable (just like when you pass to a function)
var param1 = myString;
param1 = 'world'; // Re assignment
console.log(myString); // Logs 'hello'
console.log(param1); // Logs 'world'
Teraz to samo, ale z funkcją
function myFunc(param1) {
param1 = 'world';
console.log(param1); // Logs 'world'
}
var myString = 'hello';
// Calls myFunc and assigns param1 to myString just like param1 = myString
myFunc(myString);
console.log(myString); // logs 'hello'
OK, teraz podajmy kilka przykładów użycia zamiast tego obiektów ... po pierwsze, bez funkcji.
var myObject = {
firstName: 'Joe',
lastName: 'Smith'
};
// Assign to a new variable (just like when you pass to a function)
var otherObj = myObject;
// Let's mutate our object
otherObj.firstName = 'Sue'; // I guess Joe decided to be a girl
console.log(myObject.firstName); // Logs 'Sue'
console.log(otherObj.firstName); // Logs 'Sue'
// Now, let's reassign the variable
otherObj = {
firstName: 'Jack',
lastName: 'Frost'
};
// Now, otherObj and myObject are assigned to 2 very different objects
// And mutating one object has no influence on the other
console.log(myObject.firstName); // Logs 'Sue'
console.log(otherObj.firstName); // Logs 'Jack';
Teraz to samo, ale z wywołaniem funkcji
function myFunc(otherObj) {
// Let's mutate our object
otherObj.firstName = 'Sue';
console.log(otherObj.firstName); // Logs 'Sue'
// Now let's re-assign
otherObj = {
firstName: 'Jack',
lastName: 'Frost'
};
console.log(otherObj.firstName); // Logs 'Jack'
// Again, otherObj and myObject are assigned to 2 very different objects
// And mutating one object doesn't magically mutate the other
}
var myObject = {
firstName: 'Joe',
lastName: 'Smith'
};
// Calls myFunc and assigns otherObj to myObject just like otherObj = myObject
myFunc(myObject);
console.log(myObject.firstName); // Logs 'Sue', just like before
OK, jeśli przeczytałeś cały ten post, być może teraz lepiej rozumiesz, jak działają wywołania funkcji w JavaScript. Nie ma znaczenia, czy coś jest przekazywane przez odniesienie, czy według wartości ... Liczy się przypisanie czy mutacja.
Za każdym razem, gdy przekazujesz zmienną do funkcji, "przypisujesz" jakąkolwiek nazwę zmiennej parametru, tak jak gdybyś użył znaku równości (=).
Zawsze pamiętaj, że znak równości (=) oznacza przypisanie. Zawsze pamiętaj, że przekazanie parametru do funkcji w JavaScript oznacza również przypisanie. Są takie same, a 2 zmienne są połączone dokładnie w ten sam sposób (co oznacza, że nie są, chyba że policzysz, że są przypisane do tego samego obiektu).
Jedyny przypadek, w którym „modyfikacja zmiennej” wpływa na inną zmienną, to sytuacja, w której obiekt bazowy jest zmutowany (w takim przypadku nie zmodyfikowano zmiennej, ale sam obiekt.
Nie ma sensu rozróżniać obiektów i prymitywów, ponieważ działa to dokładnie tak samo, jak gdybyś nie miał funkcji i po prostu użył znaku równości, aby przypisać nową zmienną.
Jedynym problemem jest sytuacja, gdy nazwa zmiennej, którą przekazujesz do funkcji, jest taka sama, jak nazwa parametru funkcji. Kiedy tak się dzieje, musisz traktować parametr wewnątrz funkcji tak, jakby była zupełnie nową zmienną prywatną dla funkcji (ponieważ tak jest)
function myFunc(myString) {
// myString is private and does not affect the outer variable
myString = 'hello';
}
var myString = 'test';
myString = myString; // Does nothing, myString is still 'test';
myFunc(myString);
console.log(myString); // Logs 'test'
Rozważ następujące:
- Zmienne to wskaźniki wartości w pamięci.
- Ponowne przypisanie zmiennej po prostu wskazuje ten wskaźnik na nową wartość.
- Ponowne przypisanie zmiennej nigdy nie wpłynie na inne zmienne, które wskazywały na ten sam obiekt
Więc
zapomnij o
„przekazywaniu przez odniesienie / wartość”
, nie rozłączaj się na „przekazywanie przez odniesienie / wartość”, ponieważ:
- Terminy są używane tylko do opisania zachowania języka, niekoniecznie do faktycznej podstawowej implementacji. W wyniku tej abstrakcji zostają utracone krytyczne szczegóły, które są niezbędne do rzetelnego wyjaśnienia, co nieuchronnie prowadzi do obecnej sytuacji, w której pojedynczy termin nie opisuje odpowiednio rzeczywistego zachowania i należy podać dodatkowe informacje
- Pojęcia te nie zostały pierwotnie zdefiniowane z zamiarem opisania w szczególności javascript, więc nie czuję się zmuszony do ich używania, gdy tylko zwiększają zamieszanie.
Odpowiadając na twoje pytanie: wskaźniki są przekazywane.
// code
var obj = {
name: 'Fred',
num: 1
};
// illustration
'Fred'
/
/
(obj) ---- {}
\
\
1
// code
obj.name = 'George';
// illustration
'Fred'
(obj) ---- {} ----- 'George'
\
\
1
// code
obj = {};
// illustration
'Fred'
(obj) {} ----- 'George'
| \
| \
{ } 1
// code
var obj = {
text: 'Hello world!'
};
/* function parameters get their own pointer to
* the arguments that are passed in, just like any other variable */
someFunc(obj);
// illustration
(caller scope) (someFunc scope)
\ /
\ /
\ /
\ /
\ /
{ }
|
|
|
'Hello world'
Kilka uwag końcowych:
- Kuszące jest myślenie, że prymitywy są wymuszane przez specjalne reguły, podczas gdy obiekty nie są, ale prymitywy są po prostu końcem łańcucha wskaźników.
- Jako ostatni przykład zastanów się, dlaczego zwykła próba wyczyszczenia tablicy nie działa zgodnie z oczekiwaniami.
var a = [1,2];
var b = a;
a = [];
console.log(b); // [1,2]
// doesn't work because `b` is still pointing at the original array
Obiekt znajdujący się poza funkcją jest przekazywany do funkcji przez podanie odniesienia do obiektu zewnętrznego.
Kiedy używasz tego odniesienia do manipulowania jego obiektem, wpływa to na obiekt na zewnątrz. Jeśli jednak w funkcji zdecydowałeś się wskazać odniesienie do czegoś innego, w ogóle nie wpłynąłeś na obiekt na zewnątrz, ponieważ wszystko, co zrobiłeś, to przekierowanie odniesienia do czegoś innego.
Pomyśl o tym w ten sposób: zawsze przechodzi przez wartość. Jednak wartością obiektu nie jest sam obiekt, ale odniesienie do tego obiektu.
Oto przykład przekazywania liczby (typ pierwotny)
function changePrimitive(val) {
// At this point there are two '10's in memory.
// Changing one won't affect the other
val = val * 10;
}
var x = 10;
changePrimitive(x);
// x === 10
Powtórzenie tego z obiektem daje różne wyniki:
function changeObject(obj) {
// At this point there are two references (x and obj) in memory,
// but these both point to the same object.
// changing the object will change the underlying object that
// x and obj both hold a reference to.
obj.val = obj.val * 10;
}
var x = { val: 10 };
changeObject(x);
// x === { val: 100 }
Jeszcze jeden przykład:
function changeObject(obj) {
// Again there are two references (x and obj) in memory,
// these both point to the same object.
// now we create a completely new object and assign it.
// obj's reference now points to the new object.
// x's reference doesn't change.
obj = { val: 100 };
}
var x = { val: 10 };
changeObject(x);
// x === { val: 10}
Bardzo szczegółowe wyjaśnienie dotyczące kopiowania, przekazywania i porównywania według wartości i przez odniesienie znajduje się w tym rozdziale książki „JavaScript: The Definitive Guide” .
Zanim opuścimy temat manipulowania obiektami i tablicami przez odniesienie, musimy wyjaśnić pewien punkt nazewnictwa.
Wyrażenie „przekazywanie przez odniesienie” może mieć kilka znaczeń. Dla niektórych czytelników fraza odnosi się do techniki wywoływania funkcji, która pozwala funkcji przypisywać nowe wartości do swoich argumentów i mieć te zmodyfikowane wartości widoczne poza funkcją. To nie jest sposób, w jaki termin ten jest używany w tej książce.
Tutaj mamy na myśli po prostu to, że odniesienie do obiektu lub tablicy - a nie sam obiekt - jest przekazywane do funkcji. Funkcja może używać odwołania do modyfikowania właściwości obiektu lub elementów tablicy. Ale jeśli funkcja nadpisze odniesienie odwołaniem do nowego obiektu lub tablicy, ta modyfikacja nie będzie widoczna poza funkcją.
Czytelnicy zaznajomieni z innym znaczeniem tego terminu mogą woleć powiedzieć, że obiekty i tablice są przekazywane przez wartość, ale wartość, która jest przekazywana, jest w rzeczywistości odniesieniem, a nie samym obiektem.
JavaScript jest zawsze przekazywana przez wartość ; wszystko ma charakter wartościowy.
Obiekty są wartościami, a funkcje składowe obiektów same w sobie są wartościami (pamiętaj, że funkcje są obiektami pierwszej klasy w JavaScript). Również w odniesieniu do koncepcji, że wszystko w JavaScript jest obiektem ; To jest źle. Łańcuchy, symbole, liczby, wartości logiczne, wartości null i nieokreślone są prymitywami .
Czasami mogą wykorzystać niektóre funkcje i właściwości składowe odziedziczone z ich podstawowych prototypów, ale jest to tylko dla wygody. Nie oznacza to, że same są przedmiotami. Wypróbuj poniższe w celach informacyjnych:
x = "test";
alert(x.foo);
x.foo = 12;
alert(x.foo);
W obu alertach znajdziesz wartość, która ma być niezdefiniowana.
W JavaScript typ wartości decyduje wyłącznie o tym, czy wartość ta zostanie przypisana przez kopię wartości, czy kopię referencyjną .
Wartości pierwotne są zawsze przypisywane / przekazywane przez kopię wartości :
null
undefined
- strunowy
- numer
- boolean
- symbol w
ES6
Wartości złożone są zawsze przypisywane / przekazywane przez kopię referencyjną
- obiekty
- tablice
- funkcjonować
Na przykład
var a = 2;
var b = a; // `b` is always a copy of the value in `a`
b++;
a; // 2
b; // 3
var c = [1,2,3];
var d = c; // `d` is a reference to the shared `[1,2,3]` value
d.push( 4 );
c; // [1,2,3,4]
d; // [1,2,3,4]
W powyższym fragmencie, ponieważ 2
jest prymitywem skalarnym, a
przechowuje jedną początkową kopię tej wartości i b
przypisuje jej drugą kopię wartości. Zmieniając b
, w żaden sposób nie zmieniasz wartości w a
.
Ale oba c
i d
są oddzielnymi odniesieniami do tej samej wspólnej wartości [1,2,3]
, która jest wartością złożoną. Należy zauważyć, że ani jeden, c
ani d
więcej „nie jest właścicielem” [1,2,3]
wartości - oba są po prostu równymi odniesieniami równorzędnymi do wartości. Tak więc użycie dowolnego odwołania do modyfikacji ( .push(4)
) samej rzeczywistej array
wartości współdzielonej wpływa tylko na jedną wspólną wartość, a oba odwołania będą odnosić się do nowo zmodyfikowanej wartości [1,2,3,4]
.
var a = [1,2,3];
var b = a;
a; // [1,2,3]
b; // [1,2,3]
// later
b = [4,5,6];
a; // [1,2,3]
b; // [4,5,6]
Kiedy wykonujemy przypisanie b = [4,5,6]
, nie robimy absolutnie nic, aby wpłynąć na a
to, gdzie nadal występuje odwołanie ( [1,2,3]
). Aby to zrobić, b
musiałby być raczej wskaźnikiem a
niż odniesieniem do array
- ale w JS nie ma takiej możliwości!
function foo(x) {
x.push( 4 );
x; // [1,2,3,4]
// later
x = [4,5,6];
x.push( 7 );
x; // [4,5,6,7]
}
var a = [1,2,3];
foo( a );
a; // [1,2,3,4] not [4,5,6,7]
Kiedy przekazujemy argument a
, przypisuje on kopię a
odwołania do x
. x
i a
są oddzielnymi odnośnikami wskazującymi na tę samą [1,2,3]
wartość. Teraz, wewnątrz funkcji, możemy użyć tego odwołania do zmiany samej wartości ( push(4)
). Ale kiedy dokonujemy przypisania x = [4,5,6]
, w żaden sposób nie wpływa to na to, gdzie a
wskazuje początkowe odniesienie - nadal wskazuje na (teraz zmodyfikowaną) [1,2,3,4]
wartość.
Aby skutecznie przekazać wartość złożoną (taką jak wartość array
) przez kopię wartości, musisz ręcznie wykonać jej kopię, aby przekazane odniesienie nie wskazywało nadal na oryginał. Na przykład:
foo( a.slice() );
Wartość złożona (obiekt, tablica itp.), Która może być przekazana przez kopię referencyjną
function foo(wrapper) {
wrapper.a = 42;
}
var obj = {
a: 2
};
foo( obj );
obj.a; // 42
Tutaj obj
działa jako opakowanie dla pierwotnej właściwości skalarnej a
. Po przekazaniu do foo(..)
, kopia obj
odwołania jest przekazywana i ustawiana na wrapper
parametr. Możemy teraz użyć wrapper
odwołania, aby uzyskać dostęp do udostępnionego obiektu i zaktualizować jego właściwość. Po zakończeniu funkcji obj.a
zobaczy zaktualizowaną wartość 42
.
Źródło
cóż, chodzi o „wydajność” i „szybkość”, a po prostu „zarządzanie pamięcią” w języku programowania.
w javascript możemy umieścić wartości w dwóch warstwach: typ1 - objects
i typ2 - wszystkie inne typy wartości, takie jak string
& boolean
& itp.
jeśli wyobrazisz sobie pamięć jako poniższe kwadraty, w których w każdym z nich można zapisać tylko jedną wartość typu 2:
każda wartość type2 (zielona) jest pojedynczym kwadratem, podczas gdy wartość type1 (niebieska) to ich grupa :
Chodzi o to, że jeśli chcesz wskazać wartość-type2, adres jest zwykły, ale jeśli chcesz zrobić to samo dla wartości-type1, to wcale nie jest łatwe! :
i w bardziej skomplikowanej historii:
więc tutaj mogą nas uratować referencje :
podczas gdy zielona strzałka jest tutaj typową zmienną, fioletowa jest zmienną obiektową, więc ponieważ zielona strzałka (typowa zmienna) ma tylko jedno zadanie (i to wskazuje typową wartość), nie musimy oddzielać jej wartości od więc przesuwamy zieloną strzałkę z wartością tego, gdziekolwiek się pojawi i we wszystkich przypisaniach, funkcjach i tak dalej ...
ale nie możemy zrobić tego samego z fioletową strzałką, możemy chcieć przenieść tutaj komórkę 'john' lub wiele innych rzeczy ... więc fioletowa strzałka przylgnie do swojego miejsca i tylko typowe strzałki, które zostały do niej przypisane, będą się poruszać ...
bardzo zagmatwana sytuacja polega na tym, że nie możesz zrozumieć, jak zmienia się zmienna, do której się odwołujesz, spójrzmy na bardzo dobry przykład:
let arr = [1, 2, 3, 4, 5]; //arr is an object now and a purple arrow is indicating it
let obj2 = arr; // now, obj2 is another purple arrow that is indicating the value of arr obj
let obj3 = ['a', 'b', 'c'];
obj2.push(6); // first pic below - making a new hand for the blue circle to point the 6
//obj2 = [1, 2, 3, 4, 5, 6]
//arr = [1, 2, 3, 4, 5, 6]
//we changed the blue circle object value (type1-value) and due to arr and obj2 are indicating that so both of them changed
obj2 = obj3; //next pic below - changing the direction of obj2 array from blue circle to orange circle so obj2 is no more [1,2,3,4,5,6] and it's no more about changing anything in it but we completely changed its direction and now obj2 is pointing to obj3
//obj2 = ['a', 'b', 'c'];
//obj3 = ['a', 'b', 'c'];
To trochę więcej wyjaśnienia przekazywania wartości i przekazywania przez referencję (JavaScript). W tej koncepcji mówią o przekazywaniu zmiennej przez odniesienie i przekazywaniu zmiennej przez odniesienie.
Przekaż według wartości (typ pierwotny)
var a = 3;
var b = a;
console.log(a); // a = 3
console.log(b); // b = 3
a=4;
console.log(a); // a = 4
console.log(b); // b = 3
- ma zastosowanie do wszystkich typów pierwotnych w JavaScript (ciąg, liczba, wartość logiczna, nieokreślona i null).
- a ma przydzieloną pamięć (powiedzmy 0x001), a b tworzy kopię wartości w pamięci (powiedzmy 0x002).
- Zatem zmiana wartości zmiennej nie wpływa na drugą, ponieważ obie znajdują się w dwóch różnych lokalizacjach.
Przekaż przez odniesienie (obiekty)
var c = { "name" : "john" };
var d = c;
console.log(c); // { "name" : "john" }
console.log(d); // { "name" : "john" }
c.name = "doe";
console.log(c); // { "name" : "doe" }
console.log(d); // { "name" : "doe" }
- Silnik JavaScript przypisuje obiekt do zmiennej
c
i wskazuje na jakąś pamięć, powiedzmy (0x012). - Gdy d = c, w tym kroku
d
wskazuje tę samą lokalizację (0x012). - Zmiana wartości dowolnej powoduje zmianę wartości obu zmiennych.
- Funkcje są obiektami
Przypadek specjalny, przekazywanie przez odniesienie (obiekty)
c = {"name" : "jane"};
console.log(c); // { "name" : "jane" }
console.log(d); // { "name" : "doe" }
- Operator równości (=) ustawia nową przestrzeń pamięci lub adres
dzielenie się tym, co wiem o referencjach w JavaScript
W JavaScript, podczas przypisywania obiektu do zmiennej, wartość przypisana do zmiennej jest odniesieniem do obiektu:
var a = {
a: 1,
b: 2,
c: 3
};
var b = a;
// b.c is referencing to a.c value
console.log(b.c) // Output: 3
// Changing value of b.c
b.c = 4
// Also changes the value of a.c
console.log(a.c) // Output: 4
Semantyka!! Ustalenie konkretnych definicji z konieczności sprawi, że niektóre odpowiedzi i komentarze będą niekompatybilne, ponieważ nie opisują tego samego, nawet przy użyciu tych samych słów i wyrażeń, ale ważne jest, aby ominąć zamieszanie (szczególnie w przypadku nowych programistów).
Po pierwsze, istnieje wiele poziomów abstrakcji, których nie każdy zdaje się rozumieć. Nowi programiści, którzy uczyli się języków czwartej lub piątej generacji, mogą mieć trudności z ogarnięciem umysłu wokół pojęć znanych programistom asemblera lub programistów C, które nie są stopniowane przez wskaźniki do wskaźników do wskaźników. Przekazywanie przez odwołanie nie oznacza po prostu możliwości zmiany obiektu, do którego istnieje odwołanie, przy użyciu zmiennej parametru funkcji.
Zmienna : Połączona koncepcja symbolu, który odwołuje się do wartości w określonym miejscu w pamięci. Termin ten jest zwykle zbyt obciążony, aby można go było używać samodzielnie przy omawianiu szczegółów.
Symbol : Ciąg tekstowy używany do odniesienia się do zmiennej (np. Nazwa zmiennej).
Wartość : poszczególne bity przechowywane w pamięci i do których są odniesienia przy użyciu symbolu zmiennej.
Lokalizacja pamięci : miejsce przechowywania wartości zmiennej. (Sama lokalizacja jest reprezentowana przez liczbę oddzielną od wartości przechowywanej w lokalizacji).
Parametr funkcji : zmienna zadeklarowana w definicji funkcji, używana do odwoływania się do zmiennych przekazanych do funkcji.
Argument funkcji : zmienna poza funkcją, która jest przekazywana do funkcji przez wywołującego.
Zmienna obiektu : zmienna, której podstawową wartością bazową nie jest sam „obiekt”, a raczej jej wartość jest wskaźnikiem (wartością miejsca w pamięci) do innej lokalizacji w pamięci, w której przechowywane są rzeczywiste dane obiektu. W większości języków wyższej generacji aspekt „wskaźnika” jest skutecznie ukrywany przez automatyczne usuwanie odniesień w różnych kontekstach.
Zmienna pierwotna : zmienna, której wartość JEST wartością rzeczywistą. Nawet ta koncepcja może być skomplikowana przez auto-boxing i konteksty obiektowe w różnych językach, ale ogólna koncepcja jest taka, że wartość zmiennej JEST rzeczywistą wartością reprezentowaną przez symbol zmiennej, a nie wskaźnik do innej lokalizacji w pamięci.
Argumenty i parametry funkcji to nie to samo. Ponadto wartość zmiennej nie jest obiektem zmiennej (jak już wskazywały różne osoby, ale najwyraźniej jest ignorowana). Te rozróżnienia mają kluczowe znaczenie dla właściwego zrozumienia.
Wartość przekazywana lub Call-by-sharing (dla obiektów) : wartość argumentu funkcji jest KOPIOWANA do innej lokalizacji w pamięci, do której odwołuje się symbol parametru funkcji (niezależnie od tego, czy znajduje się na stosie, czy na stercie). Innymi słowy, parametr funkcji otrzymał kopię przekazanej wartości argumentu ... ORAZ (krytyczna) wartość argumentu NIGDY NIE JEST AKTUALIZOWANA / ZMIENIONA / ZMIENIONA przez funkcję wywołującą. Pamiętaj, że wartość zmiennej obiektowej NIE jest samym obiektem, a raczej wskaźnikiem do obiektu, więc przekazanie zmiennej obiektowej według wartości powoduje skopiowanie wskaźnika do zmiennej parametru funkcji. Wartość parametru funkcji wskazuje dokładnie ten sam obiekt w pamięci. Same dane obiektu można zmienić bezpośrednio za pomocą parametru funkcji, ALE wartość argumentu funkcji NIGDY NIE JEST AKTUALIZOWANA, więc będzie wskazywać ten sam obiekt przez cały czas, a nawet po wywołaniu funkcji (nawet jeśli dane obiektu zostały zmienione lub jeśli parametrowi funkcji jest przypisany zupełnie inny obiekt). Nieprawidłowe jest stwierdzenie, że argument funkcji został przekazany przez odwołanie tylko dlatego, że obiekt, do którego się odwołuje, można aktualizować za pośrednictwem zmiennej parametru funkcji.
Wywołanie / przekazanie referencji : wartość argumentu funkcji może / zostanie zaktualizowana bezpośrednio przez odpowiedni parametr funkcji. Jeśli to pomaga, parametr funkcji staje się efektywnym „aliasem” dla argumentu - skutecznie odwołują się do tej samej wartości w tym samym miejscu pamięci. Jeśli argument funkcji jest zmienną obiektową, możliwość zmiany danych obiektu nie różni się od przypadku przekazania wartości, ponieważ parametr funkcji będzie nadal wskazywał na ten sam obiekt co argument. Ale w przypadku zmiennej obiektowej, jeśli parametr funkcji jest ustawiony na zupełnie inny obiekt, to argument będzie również wskazywał na inny obiekt - nie dzieje się tak w przypadku przekazywania przez wartość.
JavaScript nie jest przekazywany przez odwołanie. Jeśli uważnie się przeczytasz, zdasz sobie sprawę, że wszystkie przeciwne opinie nie rozumieją, co oznacza przekazywanie wartości i błędnie dochodzą do wniosku, że możliwość aktualizacji danych obiektu za pomocą parametru funkcji jest równoznaczna z „przekazaniem wartości”.
Klonowanie / kopiowanie obiektu: tworzony jest nowy obiekt, a dane oryginalnego obiektu są kopiowane. Może to być głęboka lub płytka kopia, ale chodzi o to, że tworzony jest nowy obiekt. Tworzenie kopii obiektu jest pojęciem odrębnym od przekazywania wartości. Niektóre języki rozróżniają obiekt klasy i struktury (lub tym podobne) i mogą mieć różne zachowanie przy przekazywaniu zmiennych różnych typów. Ale JavaScript nie robi niczego podobnego automatycznie podczas przekazywania zmiennych obiektowych. Ale brak automatycznego klonowania obiektów nie przekłada się na przekazywanie przez odniesienie.
JavaScript przekazuje typy pierwotne według wartości i typy obiektów przez odwołanie
Teraz ludzie lubią sprzeczać się bez końca o to, czy „przekazywanie przez odniesienie” jest właściwym sposobem opisania tego, co Java i in. faktycznie to robią. Chodzi o to:
- Przekazanie obiektu nie powoduje jego skopiowania.
- Obiekt przekazany do funkcji może mieć zmodyfikowane elementy składowe przez funkcję.
- Wartość pierwotna przekazana do funkcji nie może być modyfikowana przez funkcję. Kopia jest wykonana.
W mojej książce nazywa się to przekazywaniem przez odniesienie.
- Brian Bi - Które języki programowania są przekazywane przez odniesienie?
Aktualizacja
Oto odpowiedź na to pytanie:
W JavaScript nie ma opcji „przekazywanie przez odniesienie”.
Mój prosty sposób, aby to zrozumieć ...
Podczas wywoływania funkcji przekazujesz zawartość (referencję lub wartość) argumentów zmiennych, a nie same zmienne.
var var1 = 13; var var2 = { prop: 2 }; //13 and var2's content (reference) are being passed here foo(var1, var2);
Wewnątrz funkcji zmienne parametrów
inVar1
iinVar2
odbierają przekazywaną zawartość.function foo(inVar1, inVar2){ //changing contents of inVar1 and inVar2 won't affect variables outside inVar1 = 20; inVar2 = { prop: 7 }; }
Od momentu
inVar2
otrzymania referencji{ prop: 2 }
możesz zmienić wartość właściwości obiektu.function foo(inVar1, inVar2){ inVar2.prop = 7; }
Przekazywanie argumentów do funkcji w JavaScript jest analogiczne do przekazywania parametrów przez wartość wskaźnika w C:
/*
The following C program demonstrates how arguments
to JavaScript functions are passed in a way analogous
to pass-by-pointer-value in C. The original JavaScript
test case by @Shog9 follows with the translation of
the code into C. This should make things clear to
those transitioning from C to JavaScript.
function changeStuff(num, obj1, obj2)
{
num = num * 10;
obj1.item = "changed";
obj2 = {item: "changed"};
}
var num = 10;
var obj1 = {item: "unchanged"};
var obj2 = {item: "unchanged"};
changeStuff(num, obj1, obj2);
console.log(num);
console.log(obj1.item);
console.log(obj2.item);
This produces the output:
10
changed
unchanged
*/
#include <stdio.h>
#include <stdlib.h>
struct obj {
char *item;
};
void changeStuff(int *num, struct obj *obj1, struct obj *obj2)
{
// make pointer point to a new memory location
// holding the new integer value
int *old_num = num;
num = malloc(sizeof(int));
*num = *old_num * 10;
// make property of structure pointed to by pointer
// point to the new value
obj1->item = "changed";
// make pointer point to a new memory location
// holding the new structure value
obj2 = malloc(sizeof(struct obj));
obj2->item = "changed";
free(num); // end of scope
free(obj2); // end of scope
}
int num = 10;
struct obj obj1 = { "unchanged" };
struct obj obj2 = { "unchanged" };
int main()
{
// pass pointers by value: the pointers
// will be copied into the argument list
// of the called function and the copied
// pointers will point to the same values
// as the original pointers
changeStuff(&num, &obj1, &obj2);
printf("%d\n", num);
puts(obj1.item);
puts(obj2.item);
return 0;
}
Dla prawników języków programowania przeszedłem przez następujące sekcje ECMAScript 5.1 (który jest łatwiejszy do odczytania niż ostatnie wydanie) i posunąłem się nawet do pytania o to na liście mailingowej ECMAScript.
TL; DR : Wszystko jest przekazywane przez wartość, ale właściwości Objects są referencjami, a definicja obiektu jest przerażająco brakująca w standardzie.
Konstrukcja list argumentów
Sekcja 11.2.4 „Listy argumentów” mówi, co następuje na temat tworzenia listy argumentów składającej się tylko z 1 argumentu:
Produkcja ArgumentList: AssignmentExpression jest oceniana w następujący sposób:
- Niech ref będzie wynikiem obliczania AssignmentExpression.
- Niech arg będzie GetValue (ref).
- Zwraca listę, której jedynym elementem jest arg.
Sekcja wylicza również przypadki, w których lista argumentów ma 0 lub> 1 argumentów.
Tak więc wszystko jest przekazywane przez odniesienie.
Dostęp do właściwości obiektu
Sekcja 11.2.1 „Dostęp do właściwości”
Produkcja MemberExpression: MemberExpression [Expression] jest oceniany w następujący sposób:
- Niech baseReference będzie wynikiem oceny MemberExpression.
- Niech baseValue będzie GetValue (baseReference).
- Niech propertyNameReference będzie wynikiem oceny wyrażenia.
- Niech propertyNameValue będzie GetValue (propertyNameReference).
- Wywołaj CheckObjectCoercible (baseValue).
- Niech propertyNameString będzie ToString (propertyNameValue).
- Jeśli oceniana produkcja składniowa jest zawarta w kodzie trybu ścisłego, niech ścisłe będzie prawdziwe, w przeciwnym razie ścisłe będzie fałszem.
- Zwraca wartość typu Reference, którego wartością bazową jest baseValue i którego nazwa, do której istnieje odwołanie, to propertyNameString, i którego flaga trybu ścisłego jest ścisła.
Dlatego właściwości obiektów są zawsze dostępne jako odniesienie.
Na odniesienie
Opisano w sekcji 8.7 „Typ specyfikacji referencyjnej”, że odwołania nie są typami rzeczywistymi w języku - są używane tylko do opisania zachowania operatorów delete, typeof i przypisania.
Definicja „obiektu”
W wydaniu 5.1 zdefiniowano, że „Obiekt jest zbiorem właściwości”. Dlatego możemy wywnioskować, że wartością obiektu jest kolekcja, ale to, jaka jest wartość kolekcji, jest słabo zdefiniowana w specyfikacji i wymaga trochę wysiłku, aby ją zrozumieć.
Dokumentacja MDN wyjaśnia to jasno, nie będąc zbyt szczegółowym:
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 odniesienia do obiektów również są wartościami i są wyjątkowe: jeśli funkcja zmienia właściwości obiektu, do którego odwołuje się, ta zmiana jest widoczna poza funkcją, (...)
Źródło: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions#Description
obserwacja: jeśli obserwator nie ma możliwości zbadania pamięci bazowej silnika, nie ma sposobu, aby określić, czy niezmienna wartość zostanie skopiowana lub czy odniesienie zostanie przekazane.
JavaScript jest mniej lub bardziej niezależny od bazowego modelu pamięci. Nie ma czegoś takiego jak odniesienie ². JavaScript ma wartości . Dwie zmienne mogą mieć tę samą wartość (lub dokładniej: dwa rekordy środowiskowe mogą wiązać tę samą wartość). Jedynym typem wartości, które można modyfikować, są obiekty poprzez ich abstrakcyjne operacje [[Get]] i [[Set]]. Jeśli zapomnisz o komputerach i pamięci, to wszystko, czego potrzebujesz, aby opisać zachowanie skryptów JavaScripts, pozwala zrozumieć specyfikację.
let a = { prop: 1 };
let b = a; // a and b hold the same value
a.prop = "test"; // the object gets mutated, can be observed through both a and b
b = { prop: 2 }; // b holds now a different value
Teraz możesz zadać sobie pytanie, w jaki sposób dwie zmienne mogą mieć tę samą wartość na komputerze. Możesz wtedy zajrzeć do kodu źródłowego silnika JavaScript i najprawdopodobniej znajdziesz coś, co programista języka, w którym silnik został napisany, nazwałby referencją.
W rzeczywistości możesz powiedzieć, że JavaScript jest „przekazywana przez wartość”, podczas gdy wartość może być udostępniana, możesz powiedzieć, że JavaScript jest „przekazywana przez odniesienie”, co może być użyteczną logiczną abstrakcją dla programistów z języków niskiego poziomu. może nazwać to zachowanie „zadzwoń przez udostępnienie”. Ponieważ w JavaScript nie ma czegoś takiego jak odniesienie, wszystkie one nie są ani błędne, ani celowe. Dlatego nie sądzę, aby poszukiwanie odpowiedzi było szczególnie przydatne.
² Termin Odniesienie w specyfikacji nie jest odniesieniem w tradycyjnym znaczeniu. Jest to pojemnik na obiekt i nazwę właściwości oraz jest wartością pośrednią (np. Wartościuje a.b
do Reference { value = a, name = "b" }
). Termin odniesienie pojawia się również czasami w specyfikacji w niepowiązanych sekcjach.
Najbardziej zwięzłe wyjaśnienie, jakie znalazłem, znajduje się w przewodniku stylistycznym AirBNB :
Prymitywy : kiedy uzyskujesz dostęp do typu pierwotnego, pracujesz bezpośrednio nad jego wartością
- strunowy
- numer
- boolean
- zero
- nieokreślony
Na przykład:
var foo = 1,
bar = foo;
bar = 9;
console.log(foo, bar); // => 1, 9
Złożony : kiedy uzyskujesz dostęp do typu złożonego, pracujesz nad odniesieniem do jego wartości
- obiekt
- szyk
- funkcjonować
Na przykład:
var foo = [1, 2],
bar = foo;
bar[0] = 9;
console.log(foo[0], bar[0]); // => 9, 9
Oznacza to, że w rzeczywistości typy pierwotne są przekazywane przez wartość, a typy złożone są przekazywane przez odwołanie.
Czytałem te odpowiedzi wiele razy, ale NAPRAWDĘ ich nie zrozumiałem, dopóki nie dowiedziałem się o technicznej definicji „Zadzwoń przez udostępnienie”, jak określiła to Barbara Liskov
Semantyka wywołania przez współdzielenie różni się od wywołania przez odniesienie tym, że przypisania do argumentów funkcji w funkcji nie są widoczne dla wywołującego (w przeciwieństwie do semantyki referencji) [potrzebne cytowanie], więc np. Jeśli zmienna została przekazana, nie jest to możliwe aby zasymulować przypisanie tej zmiennej w zakresie wywołującego. Ponieważ jednak funkcja ma dostęp do tego samego obiektu co wywołujący (nie jest wykonywana kopia), mutacje tych obiektów, jeśli obiekty są zmienne, w ramach funkcji są widoczne dla wywołującego, co może wydawać się różnić od wywołania przez wartość semantyka. Mutacje zmiennego obiektu w funkcji są widoczne dla wywołującego, ponieważ obiekt nie jest kopiowany ani klonowany - jest udostępniany.
Oznacza to, że odniesienia do parametrów można zmieniać, jeśli przejdziesz i uzyskasz dostęp do samej wartości parametru. Z drugiej strony przypisanie do parametru zniknie po ocenie i jest niedostępne dla wywołującego funkcję.
W języku niskiego poziomu, jeśli chcesz przekazać zmienną przez odniesienie, musisz użyć określonej składni podczas tworzenia funkcji:
int myAge = 14;
increaseAgeByRef(myAge);
function increaseAgeByRef(int &age) {
*age = *age + 1;
}
&age
Jest odniesieniem do myAge
, ale jeśli chcesz, wartość trzeba konwertować odniesienia, używając *age
.
JavaScript to język wysokiego poziomu, który wykonuje tę konwersję za Ciebie.
Tak więc, chociaż obiekty są przekazywane przez odwołanie, język konwertuje parametr odwołania na wartość. Nie musisz używać &
definicji funkcji, aby przekazać ją przez odwołanie, ani *
w treści funkcji, aby przekonwertować odwołanie na wartość, JavaScript robi to za Ciebie.
Dlatego, gdy próbujesz zmienić obiekt wewnątrz funkcji, zastępując jego wartość (tj. age = {value:5}
), Zmiana nie jest zachowywana, ale jeśli zmieniasz jej właściwości (tj. age.value = 5
), Zachowuje się.
Ucz się więcej