Czy JavaScript jest językiem pass-by-reference lub pass-by-value?


1403

Typy prymitywne (liczba, ciąg itp.) Są przekazywane przez wartość, ale obiekty są nieznane, ponieważ oba mogą być przekazywane przez wartość (w przypadku, gdy uważamy, że zmienna przechowująca obiekt jest w rzeczywistości odniesieniem do obiektu ) i przekazywany przez odniesienie (gdy weźmiemy pod uwagę, że zmienna obiektu zawiera sam obiekt).

Chociaż na koniec nie ma to tak naprawdę znaczenia, chcę wiedzieć, jak poprawnie przedstawiać argumenty omawiające konwencje. Czy istnieje fragment specyfikacji JavaScript, który określa, jaka powinna być semantyka w tym zakresie?


2
Myślę, że przypadkowo przerzuciłeś definicje wartości przekazywanej przez wartość i przekazywanej przez odniesienie ... "przekazywanej przez wartość (w przypadku, gdy uważamy, że zmienna przechowująca obiekt jest w rzeczywistości odniesieniem do obiektu) i przekazana -by-referencja (gdy weźmiemy pod uwagę, że zmienna obiektu zawiera sam obiekt) ”
Niko Bellic

5
Tak. Bez względu na składnię, w każdym wywołaniu funkcji w dowolnym języku programowania, przekazywanie przez odniesienie oznacza, że ​​dane powiązane z przekazywaną zmienną nie są kopiowane po przekazaniu do funkcji, a zatem wszelkie zmiany dokonane przez funkcję w przekazywanej zmiennej zostaną zachowane w programie po zakończeniu wywołania funkcji. Pass-by-value oznacza, że ​​dane powiązane ze zmienną są faktycznie kopiowane po przekazaniu do funkcji, a wszelkie modyfikacje wprowadzone przez taką funkcję do takiej zmiennej zostaną utracone, gdy zmienna wyjdzie poza zakres funkcji, gdy funkcja powróci.
John Sonderson

5
To stare pytanie jest nieco toksyczne, ponieważ jego mocno oceniona odpowiedź jest nieprawidłowa. JavaScript jest ściśle przekazywaną wartością .
Pointy

6
@DanailNachev Terminologia jest niestety myląca. Chodzi o to, że „pass by value” i „pass by reference” to terminy, które poprzedzają wiele bardziej nowoczesnych funkcji języka programowania. Słowa „wartość” i „odwołanie” odnoszą się konkretnie do parametru, jaki pojawia się w wyrażeniu wywołania funkcji. JavaScript zawsze ocenia każde wyrażenie na liście parametrów wywołania funkcji przed wywołaniem funkcji, więc parametry są zawsze wartościami. Mylące jest to, że odwołania do obiektów są powszechnymi wartościami JavaScript. Nie oznacza to jednak, że jest to język „pass by referencyjny”.
Pointy

2
@DanailNachev „przekazuj przez odniesienie” oznacza konkretnie, że jeśli masz, var x=3, y=x; f(x); alert(y === x);wówczas funkcja f()może zgłosić alert, falsea nie true. W JavaScript jest to niemożliwe, więc nie jest to odnośnik. Dobrze, że można przekazywać referencje do obiektów modyfikowalnych, ale nie to oznacza „przekazywać przez referencję”. Jak powiedziałem, szkoda, że ​​terminologia jest tak myląca.
Pointy

Odpowiedzi:


1597

To jest interesujące w JavaScript. Rozważ 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 obj1ogóle nie było odniesieniem, zmiana obj1.itemnie miałaby wpływu na obj1zewnętrzną stronę funkcji.
  • Gdyby argument był właściwym odniesieniem, wszystko by się zmieniło. numbyłby 100i obj2.itemczytałby "changed".

Zamiast tego sytuacja jest taka, że ​​przekazywany element jest przekazywany według wartości. Ale przedmiot przekazywany przez wartość sam w sobie jest odniesieniem. Technicznie jest to nazywane dzieleniem przez połączenie .

W praktyce oznacza to, że jeśli zmienisz sam parametr (jak w przypadku numi obj2), nie wpłynie to na element, który został wprowadzony do parametru. Ale jeśli zmienisz WEWNĘTRZNE parametru, nastąpi propagacja z powrotem (jak w przypadku obj1).


32
Jest to dokładnie to samo (lub przynajmniej semantycznie) co C #. Obiekt ma dwa typy: Wartość (typy pierwotne) i Odniesienie.
Peter Lee

53
Myślę, że jest to również używane w Javie: referencja według wartości.
Jpnh

295
prawdziwym powodem jest to, że w changeStuff, num, obj1 i obj2 są referencjami. Kiedy zmieniasz itemwłaściwość obiektu, do którego odwołuje się obiekt obj1, zmieniasz wartość właściwości elementu, która pierwotnie była ustawiona na „niezmieniona”. Kiedy przypisujesz obj2 wartości {item: "updated"} zmieniasz odniesienie do nowego obiektu (który natychmiast wychodzi poza zakres po wyjściu z funkcji). Staje się bardziej oczywiste, co się dzieje, jeśli nazwiesz funkcję params rzeczy takie jak numf, obj1f i obj2f. Potem widzisz, że parametry ukrywały zewnętrzne nazwy zmiennych.
jinglesthula

13
@BartoNaz Nie bardzo. To, co chcesz, to przekazać referencję przez referencję, zamiast przekazać referencję według wartości. Ale JavaScript zawsze przekazuje odwołanie według wartości, tak jak przekazuje wszystko inne przez wartość. (Dla porównania, C # ma zachowanie pass-reference-by-value podobne do JavaScriptu i Java, ale umożliwia określenie pass-referen-by-referencji za pomocą refsłowa kluczowego.) Zwykle wystarczy, że funkcja zwróci nowy obiekt i zrobi przypisanie w punkcie, w którym wywołujesz funkcję. Np. foo = GetNewFoo();ZamiastGetNewFoo(foo);
Tim Goodman

55
Chociaż ta odpowiedź jest najbardziej popularna, może być nieco myląca, ponieważ stwierdza „gdyby była czysta, przekazywałaby wartość”. JavaScript to czysta wartość dodana. Ale przekazywana wartość jest referencją. Nie jest to wcale ograniczone do przekazywania parametrów. Możesz po prostu skopiować zmienną var obj1 = { item: 'unchanged' }; var obj2 = obj1; obj2.item = 'changed';i obserwować taki sam efekt jak w twoim przykładzie. Dlatego osobiście odsyłam odpowiedź Tima Goodmana
chiccodoro

476

Zawsze przechodzi przez wartość, ale dla obiektów wartością zmiennej jest odwołanie. Z tego powodu, gdy mijasz obiekt i zmieniasz jego elementy , zmiany te pozostają poza funkcją. To sprawia, że wygląda jak przekazywany przez odniesienie. Ale jeśli faktycznie zmienisz wartość zmiennej obiektowej, zobaczysz, że zmiana nie utrzymuje się, co dowodzi, że naprawdę przechodzi ona 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

14
@daylight: W rzeczywistości się mylisz; jeśli został przekazany przez const ref, próba wykonania changeObject spowodowałaby błąd, a nie tylko niepowodzenie. Spróbuj przypisać nową wartość do stałej referencji w C ++, a kompilator ją odrzuci. Z punktu widzenia użytkownika jest to różnica między przekazywaniem wartości i przekazywaniem przez stałą odniesienia.
deworde

5
@daylight: To nie jest stały ref. W changeObjectzmieniłem, xaby zawierał odwołanie do nowego obiektu. nawiasem mówiąc, x = {member:"bar"};jest równoważne temu, x = new Object(); x.member = "bar";co mówię, jest również prawdą w C #.
Tim Goodman,

2
@daylight: w przypadku C # możesz to zobaczyć spoza funkcji, jeśli użyjesz refsłowa kluczowego, możesz przekazać referencję przez referencję (zamiast domyślnego przekazywania referencji przez wartość), a następnie zmiana na wskazanie na a new Object() będzie się utrzymywać .
Tim Goodman,

11
@adityamenon Trudno odpowiedzieć „dlaczego”, ale chciałbym zauważyć, że projektanci Java i C # dokonali podobnego wyboru; to nie jest tylko jakaś dziwność JavaScript. Naprawdę, jest to bardzo konsekwentnie przekazywana przez wartość, co sprawia, że ​​ludzie mylą to, że wartość może być odniesieniem. Nie różni się niczym od przekazania wskaźnika (w wartości) w C ++, a następnie odłożenia go w celu ustawienia członków. Nikt nie byłby zaskoczony, że ta zmiana się utrzymuje. Ale ponieważ te języki usuwają wskaźnik i po cichu robią dla ciebie dereferencje, ludzie się mylą.
Tim Goodman

41
Innymi słowy, mylącą rzeczą tutaj nie jest przekazywanie przez wartość / przekazywanie przez odniesienie. Wszystko jest zgodne z wartością, kropka. Zagmatwane jest to, że nie można przekazać obiektu ani przechowywać obiektu w zmiennej. Za każdym razem, gdy myślisz , że to robisz, faktycznie przekazujesz lub przechowujesz odniesienie do tego obiektu. Ale kiedy idziesz, aby uzyskać dostęp do jego członków, dochodzi do cichej dereferencji, która utrwala fikcję, że twoja zmienna zawiera rzeczywisty obiekt.
Tim Goodman

150

Zmienna nie „trzyma” obiektu; zawiera odniesienie. Możesz przypisać to odwołanie do innej zmiennej, a teraz oba odnoszą się do tego samego obiektu. Zawsze przechodzi przez wartość (nawet jeśli ta wartość jest odniesieniem ...).

Nie ma możliwości zmiany wartości przechowywanej przez zmienną przekazywaną jako parametr, co byłoby możliwe, gdyby JavaScript obsługiwał przekazywanie przez referencję.


2
To mnie trochę myli. Czy przekazanie referencji nie jest zgodne?

8
Autor oznacza, że ​​przekazując referencję, przekazujesz wartość referencyjną (innym sposobem, aby o tym pomyśleć, jest przekazanie wartości adresu pamięci). Dlatego dlatego, jeśli ponownie zdefiniujesz obiekt, oryginał się nie zmieni, ponieważ tworzysz nowy obiekt w innym miejscu pamięci. Jeśli zmienisz właściwość, oryginalny obiekt zmieni się, ponieważ zmieniłeś go w oryginalnej lokalizacji w pamięci (która nie została ponownie przypisana).
Huy-Anh Hoang,

113

Moje dwa centy ... Tak to rozumiem. (Możesz mnie poprawić, jeśli się mylę)

Czas wyrzucić wszystko, co wiesz o przekazywaniu według wartości / referencji.

Ponieważ w JavaScript nie ma znaczenia, czy jest przekazywany przez wartość, przez referencję czy cokolwiek innego. Liczy się mutacja vs. przypisanie parametrów przekazanych 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” ... Przypisaliśmy 2 oddzielne puste obiekty do zmiennych „object1” i „object2”.

Powiedzmy, że bardziej lubimy obiekt1 ... Więc „przypisujemy” nową zmienną.

var favoriteObject = object1;

Następnie, z jakiegokolwiek powodu, decydujemy, że obiekt 2 bardziej nam się podoba. Więc po prostu wykonujemy małe przeniesienie.

favoriteObject = object2;

Nic się nie stało z obiektem1 lub obiektem2. Nie zmieniliśmy żadnych danych. Wszystko, co zrobiliśmy, to ponowne przypisanie tego, co jest naszym ulubionym przedmiotem. Ważne jest, aby wiedzieć, że zarówno obiekt2, jak i ulubiony obiekt 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, teraz spójrzmy na prymitywy takie jak na przykład łańcuchy

var string1 = 'Hello world';
var string2 = 'Goodbye world';

Ponownie wybieramy ulubionego.

var favoriteString = string1;

Zarówno nasze ulubione zmienne ciąg, jak i łańcuch1 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'

Uh oh .... Co się stało. Nie mogliśmy zmienić string1, zmieniając ulubiony ciąg ... Dlaczego? Ponieważ nie zmieniliśmy naszego obiektu ciągu . Wszystko, co zrobiliśmy, to „RE ASSIGN” zmienna favoriteString do nowego ciągu. To zasadniczo odłączyło go od string1. W poprzednim przykładzie, kiedy zmieniliśmy nazwę naszego obiektu, nie przypisaliśmy niczego. (Cóż, nie do samej zmiennej ... jednak przypisaliśmy właściwość name do nowego ciągu.) Zamiast tego po prostu zmutowaliśmy obiekt, który utrzymuje połączenia między 2 zmiennymi a obiektami leżącymi u ich podstaw. (Nawet jeśli chcieliśmy zmodyfikować lub zmutować sam obiekt łańcuchowy, nie mogliśmy tego zrobić, ponieważ w JavaScript są ciągi niezmienne.)

Teraz do funkcji i przekazywania parametrów .... Kiedy wywołujesz funkcję i przekazujesz parametr, to, co zasadniczo robisz, to „przypisanie” do nowej zmiennej, i działa dokładnie tak samo, jakbyś po prostu przypisał 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 z użyciem obiektów zamiast ... najpierw, 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 przeczytasz cały ten post, być może lepiej rozumiesz, jak wywołania funkcji działają w JavaScript. Nie ma znaczenia, czy coś jest przekazywane przez odniesienie, czy przez wartość ... Liczy się przypisanie vs mutacja.

Za każdym razem, gdy przekazujesz zmienną do funkcji, „przypisujesz” do dowolnej nazwy zmiennej parametru, tak jak w przypadku użycia 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 dwie zmienne są połączone dokładnie w ten sam sposób (to znaczy tak nie jest, chyba że policzysz, że są przypisane do tego samego obiektu).

Jedyny moment, w którym „modyfikowanie zmiennej” wpływa na inną zmienną, następuje po zmutowaniu obiektu leżącego u podstaw (w którym to przypadku nie zmodyfikowano zmiennej, ale sam obiekt.

Nie ma sensu wprowadzać rozróżnienia między obiektami a prymitywami, ponieważ działa on tak samo dokładnie, jakbyś nie miał funkcji, a jedynie użył znaku równości do przypisania nowej zmiennej.

Jedynym gotcha jest to, że 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 to zupełnie nowa zmienna prywatna 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'

2
Dla wszystkich programistów C, pomyśl o char *. foo(char *a){a="hello";} nic nie robi, ale jeśli to zrobisz foo(char *a){a[0]='h';a[1]='i';a[2]=0;}, zostanie zmieniony na zewnątrz, ponieważ ajest to miejsce w pamięci przekazane przez wartość, która odwołuje się do łańcucha znaków (tablica znaków). Przekazywanie struktur (podobnych do obiektów js) przez wartość w C jest dozwolone, ale nie zalecane. JavaScript po prostu egzekwuje te najlepsze praktyki i ukrywa niepotrzebne i zwykle niepożądane cruft ... a na pewno ułatwia czytanie.
technozaur

2
Jest to poprawne - terminy „ wartość według” i „ wartość odniesienia” mają znaczenie w projektowaniu języka programowania, a znaczenia te nie mają nic wspólnego z mutacją obiektów. Chodzi o to, jak działają parametry funkcji.
Pointy

2
Teraz, gdy rozumiem, że obj1 = obj2 oznacza, że ​​zarówno obj1, jak i obj2 wskazują teraz tę samą lokalizację odniesienia, a jeśli zmodyfikuję elementy wewnętrzne obj2, odwołanie się do obj1 spowoduje ujawnienie tych samych elementów wewnętrznych. Jak skopiować obiekt, aby po wykonaniu source = { "id":"1"}; copy = source /*this is wrong*/; copy.id="2"tego źródła nadal było {"id": "1"}?
Machtyn

1
Opublikowałem kolejną odpowiedź z tradycyjnymi definicjami, aby, mam nadzieję, zmniejszyć zamieszanie. Tradycyjne definicje „pass-by-value” i „pass-by-reference” zostały zdefiniowane jeszcze w dniu wskaźników pamięci przed automatycznym dereferencją. Było doskonale zrozumiałe, że wartością zmiennej obiektu była w rzeczywistości lokalizacja wskaźnika pamięci, a nie obiekt. Chociaż dyskusja nad przypisaniem vs mutacją jest być może przydatna, nie trzeba wyrzucać tradycyjnych terminów ani ich definicji. Mutacja, przypisanie, przekazywanie według wartości, przekazywanie przez referencje itp. Nie mogą być ze sobą sprzeczne.
C Perkins

czy „liczba” też jest „niezmienna”?
ebram khalil

72

Rozważ następujące:

  1. Zmienne są wskaźnikami do wartości w pamięci.
  2. Ponowne przypisanie zmiennej jedynie wskazuje ten wskaźnik na nową wartość.
  3. Ponowne przypisanie zmiennej nigdy nie wpłynie na inne zmienne wskazujące na ten sam obiekt

Więc zapomnij o „przekaż przez referencję / wartość” , nie rozłączaj się przy „przekaż przez referencję / wartość”, ponieważ:

  1. Terminy są używane tylko do opisania zachowania języka, niekoniecznie rzeczywistej implementacji. W wyniku tej abstrakcji utracone zostają kluczowe szczegóły, które są niezbędne do rzetelnego wyjaśnienia, co nieuchronnie prowadzi do obecnej sytuacji, w której pojedynczy termin nie opisuje adekwatnie faktycznego zachowania i należy podać dodatkowe informacje
  2. 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.

Aby odpowiedzieć 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, ale prymitywy są po prostu końcem łańcucha wskaźnika.
  • Jako ostatni przykład zastanów się, dlaczego wspólna 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

Dalsze pytania dotyczące dodatkowego kredytu;) Jak działa wywóz śmieci? Jeśli cyklicznie zmieniam zmienną przez milion {'George', 1}wartości, ale używam tylko jednej z nich na raz, to jak zarządza się innymi? A co się stanie, gdy przypiszę zmienną do wartości innej zmiennej? Czy następnie wskazuję wskaźnik, czy wskazuję wskazówkę właściwego operandu? Czy var myExistingVar = {"blah", 42}; var obj = myExistingVar;skutkuje objwskazaniem {"blah", 42}lub myExistingVar?
Michael Hoffmann

@MichaelHoffmann Te zasługują na własne SO pytania i prawdopodobnie są już na nie lepiej udzielone odpowiedzi, niż potrafię. To powiedziawszy, 1)uruchomiłem profil pamięci w narzędziach programistycznych przeglądarki dla funkcji pętli, takiej jak ta, którą opisałeś i zauważyłem gwałtowne zużycie pamięci podczas całego procesu zapętlania. Wydaje się to wskazywać, że w trakcie każdej iteracji pętli rzeczywiście powstają nowe identyczne obiekty. Gdy kolce nagle spadają, śmieciarz po prostu wyczyścił grupę tych nieużywanych obiektów.
geg

1
@MichaelHoffmann 2)Jeśli chodzi o coś takiego var a = b, javascript nie zapewnia mechanizmu używania wskaźników, więc zmienna nigdy nie może wskazywać na wskaźnik (jak w C), chociaż niewątpliwie korzysta z niego silnik javascript. Więc ... var a = bwskaże a„na punkt właściwego operandu”
geg

Zadałem tutaj pytanie nr 1 (szczególnie o Chrome, ponieważ implementacja jest prawdopodobnie inna w każdej przeglądarce) stackoverflow.com/q/42778439/539997 i wciąż próbuję wymyślić, jak sformułować pytanie nr 2. Każda pomoc jest mile widziana.
Michael Hoffmann

1
Nie trzeba zapominać o „przekazaniu przez referencję / wartość” ! Terminy te mają znaczenie historyczne, które dokładnie opisują to, co próbujesz opisać. Jeśli wyrzucimy historyczne terminy i definicje i staniemy się zbyt leniwi, aby dowiedzieć się, co pierwotnie mieli na myśli, tracimy zdolność do skutecznego komunikowania się między pokoleniami. Nie byłoby dobrego sposobu na omówienie różnic między różnymi językami i systemami. Zamiast tego nowi programiści muszą nauczyć się i zrozumieć tradycyjne warunki oraz dlaczego i skąd pochodzą. W przeciwnym razie wspólnie tracimy wiedzę i zrozumienie.
C Perkins

24

Obiekt poza funkcją jest przekazywany do funkcji przez podanie odwołania do obiektu zewnętrznego.

Kiedy używasz tego odwołania do manipulowania jego obiektem, wpływa to na obiekt na zewnątrz. Jeśli jednak wewnątrz funkcji postanowiłeś 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.


20

Pomyśl o tym w ten sposób: zawsze chodzi o wartość. Jednak wartością obiektu nie jest sam obiekt, ale odniesienie do tego obiektu.

Oto przykład, przekazując liczbę (prymitywny typ)

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}

19

Bardzo szczegółowe wyjaśnienie na temat kopiowania, przekazywania i porównywania według wartości i przez odniesienie znajduje się w tym rozdziale książki „JavaScript: Przewodnik definitywny” .

Zanim przejdziemy do tematu manipulowania obiektami i tablicami przez odniesienie, musimy wyjaśnić punkt nazewnictwa.

Wyrażenie „przekazać 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 jej argumentów i wyświetlać te zmodyfikowane wartości poza funkcją. Nie jest to sposób, w jaki termin jest używany w tej książce.

Mamy tu na myśli po prostu to, że odwołanie do obiektu lub tablicy - a nie do samego obiektu - 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 zastąpi odwołanie odwołaniem do nowego obiektu lub tablicy, ta modyfikacja nie będzie widoczna poza funkcją.

Czytelnicy zaznajomieni z innym znaczeniem tego terminu mogą powiedzieć, że obiekty i tablice są przekazywane przez wartość, ale przekazywana wartość jest tak naprawdę odniesieniem, a nie samym obiektem.


Wow, to jest niezwykle mylące. Kto przy zdrowych zmysłach zdefiniuje ugruntowany termin oznaczający dokładne przeciwieństwo, a następnie użyje go w ten sposób? Nic dziwnego, że tak wiele odpowiedzi na to pytanie jest tak zdezorientowanych.
Jörg W Mittag

16

JavaScript jest zawsze wartością przekazywaną ; wszystko ma wartość.

Obiekty są wartościami, a funkcje składowe obiektów same w sobie (pamiętaj, że funkcje są pierwszorzędnymi obiektami w JavaScript). Także w odniesieniu do koncepcji, że wszystko w JavaScript jest przedmiotem ; To jest źle. Ciągi, symbole, liczby, logiczne, null i niezdefiniowane są prymitywami .

Czasami mogą korzystać z niektórych funkcji członkowskich i właściwości odziedziczonych z ich podstawowych prototypów, ale jest to tylko dla wygody. Nie oznacza to, że same są obiektami. Wypróbuj następujące informacje:

x = "test";
alert(x.foo);
x.foo = 12;
alert(x.foo);

W obu alertach znajdziesz wartość, która jest niezdefiniowana.


12
-1, nie zawsze jest przekazywana wartość. Z MDC: „Jeśli przekazujesz obiekt (tj. Wartość inną niż pierwotna, taka jak Array lub obiekt zdefiniowany przez użytkownika) jako parametr, odwołanie do obiektu jest przekazywane do funkcji.”
Nick

37
@Nick: Zawsze przekazuje wartość. Kropka. Odwołanie do obiektu jest przekazywane przez funkcję do wartości . To nie przechodzi przez odniesienie. „Przekaż przez referencję” można niemal uznać za przekazanie samej zmiennej, a nie jej wartości; wszelkie zmiany, które funkcja wprowadza w argumencie (w tym całkowite zastąpienie go innym obiektem!) zostaną odzwierciedlone w wywołaniu. Ten ostatni bit nie jest możliwy w JS, ponieważ JS nie przechodzi przez referencję - przekazuje referencje według wartości. To rozróżnienie jest subtelne, ale raczej ważne dla zrozumienia jego ograniczeń.
cHao

1
Dla przyszłych układaczy ... O twoim odwołaniu: x = "teste"; x.foo = 12;itd. To, że właściwość nie jest trwała, nie oznacza, że ​​nie jest obiektem. Jak mówi MDN: w JavaScript prawie wszystko jest przedmiotem. Wszystkie typy pierwotne oprócz null i undefined są traktowane jak obiekty. Można im przypisać właściwości (przypisane właściwości niektórych typów nie są trwałe) i mają wszystkie cechy obiektów. link
slacktracer

9
MDN jest wiki edytowaną przez użytkownika i jest tam niepoprawna. Normatywnym odniesieniem jest ECMA-262. Patrz S. 8 „Typ specyfikacji odniesienia”, który wyjaśnia, w jaki sposób są rozwiązywane odniesienia, a także 8.12.5 „[[Put]]”, który jest używany do wyjaśnienia wyrażenia AssignmentExpression w odniesieniu do odniesienia, a dla konstruowania obiektu 9.9 ToObject. W przypadku prymitywnych wartości Michael wyjaśnił już, co robi ToObject, jak w specyfikacji. Ale patrz także s. 4.3.2 wartość pierwotna.
Garrett

1
@WonderLand: Nie, nie jest. Ludzie, którzy nigdy nie byli w stanie przejść przez odniesienie, mogą nigdy nie zrozumieć różnic między przejściem przez odniesienie a przejściem przez wartość. Ale oni tam są i mają znaczenie. Nie dbam o to, aby wprowadzać w błąd ludzi, ponieważ to brzmi łatwiej.
cHao

12

W JavaScript typ wartości kontroluje wyłącznie , czy ta wartość zostanie przypisana przez kopiowanie wartości, czy kopiowanie referencyjne .

Wartości pierwotne są zawsze przypisywane / przekazywane przez kopiowanie wartości :

  • null
  • undefined
  • strunowy
  • numer
  • boolean
  • symbol w ES6

Wartości złożone są zawsze przypisywane / przekazywane przez kopię referencyjną

  • przedmioty
  • 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ż 2jest prymitywem skalarnym, azawiera jedną początkową kopię tej wartości i bprzypisuje jej inną kopię. Zmieniając b, w żaden sposób nie zmieniasz wartości w a.

Ale oba ci dsą oddzielne odniesienia do tej samej wartości dzielonego [1,2,3], która jest wartością związek. Ważne jest, aby pamiętać, że cani dwięcej, ani „nie posiada” [1,2,3]wartości - oba są po prostu równymi odniesieniami do wartości. Tak więc, gdy używasz dowolnego odwołania do modyfikacji ( .push(4)) rzeczywistej wspólnej arraywartości, wpływa to 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 to zadanie b = [4,5,6], nie robimy absolutnie nic, aby wpłynąć na ato, gdzie nadal odwołuje się ( [1,2,3]). Aby to zrobić, bmusiałby być wskaźnikiem azamiast odniesienia do array- ale 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ę aodwołania do x. xi asą osobnymi odniesieniami wskazującymi tę samą [1,2,3]wartość. Teraz wewnątrz funkcji możemy użyć tego odwołania do mutacji samej wartości ( push(4)). Ale kiedy dokonujemy przypisania x = [4,5,6], nie ma to żadnego wpływu na to, gdzie awskazuje początkowe odniesienie - nadal wskazuje na (teraz zmodyfikowaną) [1,2,3,4]wartość.

Aby skutecznie przekazać wartość złożoną (jak an array) przez kopiowanie wartości, musisz ręcznie wykonać jej kopię, aby przekazane odwołanie nadal nie wskazywało na oryginał. Na przykład:

foo( a.slice() );

Wartość złożona (obiekt, tablica itp.), Którą można przekazać przez kopię referencyjną

function foo(wrapper) {
    wrapper.a = 42;
}

var obj = {
    a: 2
};

foo( obj );

obj.a; // 42

Tutaj objdziała jako opakowanie dla pierwotnej właściwości skalarnej a. Po przekazaniu foo(..)kopia objreferencji jest przekazywana i ustawiana na wrapperparametr. Teraz możemy użyć wrapperodwołania, aby uzyskać dostęp do współdzielonego obiektu i zaktualizować jego właściwość. Po zakończeniu funkcji obj.azobaczy zaktualizowaną wartość 42.

Źródło


Najpierw podajesz „Wartości złożone są zawsze przypisywane / przekazywane przez kopię referencyjną”, a następnie stwierdzasz „przypisuje kopię odwołania do x”. W przypadku tak zwanej „wartości złożonej” rzeczywista wartość zmiennej JEST odniesieniem (tzn. Wskaźnikiem pamięci). Tak jak wyjaśniłeś, odwołanie jest kopiowane ... więc wartość zmiennych jest kopiowana , ponownie podkreślając, że ODNIESIENIE JEST WARTOŚCIĄ. Oznacza to, że JavaScript jest wartością przekazywaną dla wszystkich typów. Przekazywanie przez wartość oznacza przekazywanie kopii wartości zmiennych. Nie ma znaczenia, że ​​wartość jest odwołaniem do obiektu / tablicy.
C Perkins

Wprowadzasz nową terminologię (kopia wartości / kopia referencyjna), co tylko komplikuje sprawę. Są tylko kopie, kropka. Jeśli przekazujesz operację podstawową, przekazałeś kopię rzeczywistych danych pierwotnych, jeśli przekazałeś obiekt, przekazałeś kopię lokalizacji pamięci obiektu. To wszystko, co musisz powiedzieć. Coś więcej tylko dezorientuje ludzi.
Scott Marcus,

9

cóż, chodzi o „wydajność” i „szybkość” oraz w prostym słowie „zarządzanie pamięcią” w języku programowania.

w javascript możemy umieścić wartości na dwóch warstwach: type1 - objectsi type2 - wszystkie inne typy wartości, takie jak string& boolean& etc

jeśli wyobrażasz sobie pamięć jako kwadraty poniżej, w których w każdym z nich można zapisać tylko jedną wartość type2:

wprowadź opis zdjęcia tutaj

każda wartość type2 (zielona) to pojedynczy kwadrat, podczas gdy wartość type1 (niebieski) to ich grupa :

wprowadź opis zdjęcia tutaj

chodzi o to, że jeśli chcesz wskazać wartość typu 2, adres jest prosty, ale jeśli chcesz zrobić to samo dla wartości typu 1, co wcale nie jest łatwe! :

wprowadź opis zdjęcia tutaj

i w bardziej skomplikowanej historii:

wprowadź opis zdjęcia tutaj

więc referencje mogą nas uratować: wprowadź opis zdjęcia tutaj

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 oznacza typową wartość), nie musimy oddzielać jej wartości od przesuwamy więc zieloną strzałkę z wartością tego, gdziekolwiek się ona znajduje, we wszystkich zadaniach, funkcjach i tak dalej ...

ale nie możemy zrobić tego samego z fioletową strzałką, możemy chcieć przenieść tu komórkę „Johna” lub wiele innych rzeczy ... więc fioletowa strzała przylgnie do tego miejsca i tylko typowe przypisane do niej strzałki będą się poruszać ...

bardzo myląca jest sytuacja, w której nie możesz zrozumieć, jak zmienia się twoja zmienna, do której się odwołujemy, 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'];

wprowadź opis zdjęcia tutaj wprowadź opis zdjęcia tutaj


8

To trochę więcej wyjaśnień dla przekazywania przez wartość i przekazywania przez referencję (JavaScript). W tej koncepcji mówią o przekazywaniu zmiennej przez referencję i przekazywaniu zmiennej przez referencję.

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
  • dotyczy wszystkich typów pierwotnych w JavaScript (ciąg, liczba, wartość logiczna, niezdefiniowana i null).
  • a jest przydzielana 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" }
  • Mechanizm JavaScript przypisuje obiekt do zmiennej ci wskazuje na pewną pamięć, powiedzmy (0x012).
  • Gdy d = c, w tym kroku dwskazuje tę samą lokalizację (0x012).
  • Zmiana wartości dowolnych zmian wartości dla obu zmiennych.
  • Funkcje są obiektami

Przypadek specjalny, przekazany przez odniesienie (obiekty)

c = {"name" : "jane"};
console.log(c); // { "name" : "jane" }
console.log(d); // { "name" : "doe" }
  • Operator równości (=) konfiguruje nową przestrzeń pamięci lub adres

W tak zwanym przypadku szczególnym przypadek przydzielania miejsca w pamięci nie jest operatorem przypisania, lecz dosłownie obiekt . Notacja nawiasu klamrowego powoduje utworzenie nowego obiektu. Właściwość cjest ustawiona na kopię odwołania do nowego obiektu.
georgeawg

6

dzielenie się tym, co wiem o referencjach w JavaScript

W JavaScript, gdy przypisujesz obiekt 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


1
To zbyt uproszczona odpowiedź, która nie mówi nic, czego wcześniejsze odpowiedzi nie wyjaśniły lepiej. Jestem zdezorientowany, dlaczego wywołujesz tablice jako specjalny przypadek.
Quentin,

1
obiekty są przechowywane jako odniesienia ” wprowadza w błąd. Myślę, że masz na myśli to, że przypisując obiekt do zmiennej, wartość przypisana do zmiennej jest odniesieniem do obiektu.
RobG

nie rozwiązuje to problemu aktualizacji obiektu wewnątrz funkcji, która nie aktualizuje obiektu poza funkcją. To jest cały obraz, w którym wydaje się działać jako wartości zamiast odniesienia. Stąd -1
amaster

@amaster Dzięki za zwrócenie na to uwagi! Czy możesz zasugerować edycję?
xameeramir

Haha, próbowałem ... moja sugerowana edycja zmieniła się zbyt mocno i nie było dozwolone
amaster

4

Semantyka!! Ustalenie konkretnych definicji z konieczności spowoduje, że niektóre odpowiedzi i komentarze będą niezgodne, ponieważ nie opisują tego samego, nawet przy użyciu tych samych słów i fraz, ale bardzo ważne jest, aby uniknąć zamieszania (szczególnie dla nowych programistów).

Przede wszystkim istnieje wiele poziomów abstrakcji, których nie wszyscy zdają się rozumieć. Nowi programiści, którzy nauczyli się języków 4. lub 5. generacji, mogą mieć trudności z koncentracją się na pojęciach znanych z asemblera lub programistami C, którzy nie są podzieleni na stopnie na wskaźniki na wskaźniki. Przekazywanie przez odniesienie nie oznacza po prostu możliwości zmiany obiektu, do którego istnieje odwołanie, za pomocą 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 stosować w pojedynkę przy omawianiu szczegółów.

Symbol : Ciąg tekstowy używany w odniesieniu do zmiennej (tj. Nazwa zmiennej).

Wartość : poszczególne bity przechowywane w pamięci i do których odwołuje się symbol zmiennej.

Lokalizacja pamięci : gdzie przechowywana jest wartość 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 przekazywanych do funkcji.

Argument funkcji : Zmienna poza funkcją, która jest przekazywana do funkcji przez program wywołujący.

Zmienna obiektowa : Zmienna, której podstawową wartością bazową nie jest sam „obiekt”, a raczej jej wartość jest wskaźnikiem (wartością lokalizacji 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 ukryty przez automatyczne usuwanie odnośników w różnych kontekstach.

Zmienna pierwotna : Zmienna, której wartość JEST wartością rzeczywistą. Nawet ta koncepcja może być skomplikowana przez automatyczne boxowanie i konteksty obiektowe różnych języków, ale ogólne idee są takie, że wartość zmiennej JEST rzeczywistą wartością reprezentowaną przez symbol zmiennej, a nie wskaźnik do innej lokalizacji pamięci.

Argumenty funkcji i parametry to nie to samo. Ponadto wartość zmiennej nie jest przedmiotem zmiennej (jak już zauważyli różni ludzie, ale najwyraźniej zostały zignorowane). Te rozróżnienia mają kluczowe znaczenie dla właściwego zrozumienia.

Przekazywanie według wartości 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 stosie). Innymi słowy, parametr funkcji otrzymał kopię wartości przekazanego argumentu ... ORAZ (krytyczny) wartość argumentu NIE JEST NIGDY 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 przez wartość kopiuje wskaźnik do zmiennej parametru funkcji. Wartość parametru funkcji wskazuje dokładnie ten sam obiekt w pamięci. Same dane obiektowe można zmienić bezpośrednio za pomocą parametru funkcji, ALE wartość argumentu funkcji NIE JEST NIGDY AKTUALIZOWANA, więc nadal będzie wskazywać na to samoobiekt w trakcie, a nawet po wywołaniu funkcji (nawet jeśli dane jego obiektu zostały zmienione lub jeśli parametrowi funkcji przypisano zupełnie inny obiekt). Błędne jest stwierdzenie, że argument funkcji został przekazany przez referencję tylko dlatego, że obiekt, do którego istnieje odwołanie, można aktualizować za pomocą zmiennej parametru funkcji.

Wywołanie / przekazanie przez odniesienie : Wartość argumentu funkcji może / zostanie zaktualizowana bezpośrednio przez odpowiedni parametr funkcji. Jeśli to pomaga, parametr funkcji staje się efektywnym „aliasem” argumentu - efektywnie odnoszą się do tej samej wartości w tym samym miejscu w pamięci. Jeśli argument funkcji jest zmienną obiektu, możliwość zmiany danych obiektu nie różni się od przypadku przekazywania wartości, ponieważ parametr funkcji nadal wskazuje ten sam obiekt co argument. Ale w przypadku zmiennej obiektowej, jeśli parametr funkcji jest ustawiony na zupełnie inny obiekt, wówczas argument również będzie wskazywał na inny obiekt - nie dzieje się tak w przypadku przekazywania wartości.

JavaScript nie przechodzi przez odniesienie. Jeśli będziesz czytał uważnie, zdasz sobie sprawę, że wszystkie przeciwne opinie błędnie rozumieją, co rozumie się przez wartość przekazywaną, i fałszywie wyciągają wniosek, że możliwość aktualizacji danych obiektu za pomocą parametru funkcji jest synonimem „przekazywanej przez wartość”.

Klonowanie / kopiowanie obiektu: tworzony jest nowy obiekt, a dane oryginalnego obiektu są kopiowane. Może to być kopia głęboka lub płytka, ale chodzi o to, że tworzony jest nowy obiekt. Tworzenie kopii obiektu jest odrębnym pojęciem od wartości przekazywanej. Niektóre języki rozróżniają obiekt klasy i struktury (lub tym podobne) i mogą mieć różne zachowanie do przekazywania zmiennych różnych typów. Ale JavaScript nie robi nic takiego automatycznie podczas przekazywania zmiennych obiektowych. Ale brak automatycznego klonowania obiektów nie przekłada się na przekazywanie przez odniesienie.


4

JavaScript przekazuje typy pierwotne według wartości, a typy obiektów przez referencję

Teraz ludzie lubią bez końca sprzeczać się, czy „przekazanie przez referencję” jest właściwym sposobem na opisanie tego, co Java i in. faktycznie tak. Chodzi o to, że:

  1. Przekazanie obiektu nie powoduje skopiowania obiektu.
  2. Obiekt przekazany do funkcji może mieć zmodyfikowane przez nią elementy.
  3. Prymitywna wartość przekazana do funkcji nie może być modyfikowana przez funkcję. Zostanie wykonana kopia.

W mojej książce, która nazywa się przekazywaniem przez odniesienie.

- Brian Bi - Jakie języki programowania są przekazywane przez odniesienie?


Aktualizacja

Oto odpowiedź na to:

W JavaScript nie ma opcji „pass by reference”.


@Amy Ponieważ to opisuje przekazanie przez wartość, a nie przekazanie przez odwołanie. Ta odpowiedź jest dobra i pokazuje różnicę: stackoverflow.com/a/3638034/3307720
nasch

@nasch Rozumiem różnicę. # 1 i # 2 opisują semantykę pass-by-ref. # 3 opisuje semantykę przekazywania według wartości.
Amy

@Amy 1, 2 i 3 są zgodne z wartością przekazywaną. Aby przejść przez referencję, potrzebujesz również 4: przypisanie referencji do nowej wartości w funkcji (za pomocą operatora =) również ponownie przypisuje referencję poza funkcję. Nie jest tak w przypadku Javascript, dzięki czemu jest on przekazywany wyłącznie przez wartość. Podczas przekazywania obiektu przekazujesz wskaźnik do obiektu i przekazujesz ten wskaźnik według wartości.
nasch

To nie jest ogólnie to, co należy rozumieć przez „pass-by-reference”. Zaspokoiłeś moje zapytanie i nie zgadzam się z tobą. Dzięki.
Amy

„W mojej książce, która nazywa się przekazywaniem przez referencje”. - W każdej książce kompilatora, książce tłumacza, książce teorii języka programowania i książce o informatyce, jaką kiedykolwiek napisano, tak nie jest.
Jörg W Mittag

3

Mój prosty sposób na zrozumienie tego ...

  • Podczas wywoływania funkcji przekazujesz zawartość (odwołanie lub wartość) zmiennych argumentu, 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 inVar1i inVar2, odbieramy przekazywaną treść.

    function foo(inVar1, inVar2){
        //changing contents of inVar1 and inVar2 won't affect variables outside
        inVar1 = 20;
        inVar2 = { prop: 7 };
    }
  • Po inVar2otrzymaniu odwołania do { prop: 2 }możesz zmienić wartość właściwości obiektu.

    function foo(inVar1, inVar2){
        inVar2.prop = 7; 
    }

3

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;
}

1
Nie sądzę, że tak jest w JavaScript: `` javascript var num = 5;
Danail Nachev

@DanailNachev: Choć może to być technicznie prawdą, różnica jest zauważalna tylko dla obiektów zmiennych, które nie są prymitywami ECMAScript.
Jörg W Mittag

3

W przypadku prawników zajmujących się językiem programowania przejrzałem następujące sekcje ECMAScript 5.1 (który jest łatwiejszy do odczytania niż najnowsze wydanie) i posunąłem się do pytania na liście mailingowej ECMAScript.

TL; DR : Wszystkie rzeczy są przekazywane przez wartość, ale właściwości Objects są referencjami, a definicja obiektu jest przerażająco brakuje w standardzie.

Konstrukcja list argumentów

Sekcja 11.2.4 „Listy argumentów” mówi o tworzeniu listy argumentów składającej się tylko z 1 argumentu:

ArgumentList produkcji: AssignmentExpression jest oceniany w następujący sposób:

  1. Niech ref będzie wynikiem oceny AssignmentExpression.
  2. Niech arg będzie GetValue (ref).
  3. Zwraca listę, której jedynym przedmiotem jest arg.

W tej sekcji wymieniono również przypadki, w których lista argumentów zawiera 0 lub> 1 argumentów.

Zatem wszystko jest przekazywane przez odniesienie.

Dostęp do właściwości obiektu

Sekcja 11.2.1 „Akcesorium nieruchomości”

Produkcja MemberExpression: MemberExpression [Expression] jest oceniana w następujący sposób:

  1. Niech baseReference będzie wynikiem oceny MemberExpression.
  2. Niech baseValue będzie GetValue (baseReference).
  3. Niech propertyNameReference będzie wynikiem oceny wyrażenia.
  4. Niech propertyNameValue będzie GetValue (propertyNameReference).
  5. Wywołaj CheckObjectCoercible (baseValue).
  6. Niech propertyNameString będzie ToString (propertyNameValue).
  7. Jeśli oceniana produkcja syntaktyczna jest zawarta w kodzie trybu ścisłego, niech ścisłe będzie prawdziwe, w przeciwnym razie ścisłe będzie fałszywe.
  8. Zwraca wartość typu Odwołanie, którego wartością podstawową jest baseValue, a nazwa, do której istnieje odwołanie, to właściwośćNazwaString, a flaga trybu ścisłego jest ścisła.

Zatem właściwości obiektów są zawsze dostępne jako odniesienie.

W sprawie odniesienia

W sekcji 8.7 „Typ specyfikacji odniesienia” opisano, że odwołania nie są prawdziwymi typami w języku - służą jedynie do opisania zachowania operatorów usuwania, typeof i przypisania.

Definicja „obiektu”

W wydaniu 5.1 zdefiniowano, że „Obiekt jest zbiorem właściwości”. Dlatego możemy wnioskować, że wartością obiektu jest kolekcja, ale co do wartości kolekcji jest źle zdefiniowane w specyfikacji i wymaga trochę wysiłku, aby zrozumieć.


Nigdy nie przestaje mnie zadziwiać, ilu ludzi jest zdezorientowanych rozróżnieniem między argumentami przekazywanymi przez wartość, argumentami przekazywanymi przez referencje, operacjami na całych obiektach i operacjami na ich właściwościach. W 1979 roku nie dostałem dyplomu informatyki, zamiast tego wybrałem 15 godzin zajęć z CS do mojego programu MBA. Niemniej jednak szybko stało się dla mnie jasne, że rozumiem te pojęcia co najmniej tak dobrze, jak to posiadali moi koledzy z dyplomem informatyki lub matematyki. Studium asemblera, a stanie się całkiem jasne.
David A. Gray

3

Dokumenty MDN wyjaśniają to jasno, nie będąc zbyt gadatliwym:

Parametry wywołania funkcji są argumentami funkcji . Argumenty są przekazywane do funkcji według wartości . Jeśli funkcja zmienia wartość argumentu, zmiana ta nie jest odzwierciedlana globalnie ani w funkcji wywołującej. Jednak odwołania do obiektów również są wartościami i są wyjątkowe: jeśli funkcja zmienia właściwości obiektu, do którego się odnosi, zmiana ta jest widoczna poza funkcją, (...)

Źródło: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions#Description


1

W języku niskiego poziomu, jeśli chcesz przekazać zmienną przez referencję, musisz użyć określonej składni przy tworzeniu funkcji:

int myAge = 14;
increaseAgeByRef(myAge);
function increaseAgeByRef(int &age) {
  *age = *age + 1;
}

&ageJest odniesieniem do myAge, ale jeśli chcesz, wartość trzeba konwertować odniesienia, używając *age.

Javascript jest językiem wysokiego poziomu, który wykonuje tę konwersję za Ciebie. Tak więc, chociaż obiekty są przekazywane przez referencję, język konwertuje parametr referencyjny na wartość. Nie musisz używać &definicji funkcji, aby przekazać ją przez odwołanie, ani *w ciele funkcji, aby przekonwertować odwołanie na wartość, JS robi to za Ciebie.

Właśnie dlatego, gdy próbujesz zmienić obiekt wewnątrz funkcji, zastępując jego wartość (tj. age = {value:5}), Zmiana nie jest utrzymywana, ale jeśli zmienisz jej właściwości (tj. age.value = 5), Tak się stanie.

Ucz się więcej


1

Przeczytałem te odpowiedzi wiele razy, ale NAPRAWDĘ nie dostałem, dopóki nie dowiedziałem się o technicznej definicji „Dzwoń przez udostępnianie”, jak to określiła Barbara Liskov

Semantyka wywołania przez udostępnianie różni się od wywołania przez odwołanie tym, że przypisania argumentów funkcji w funkcji nie są widoczne dla wywołującego (w przeciwieństwie do semantyki odniesienia) [potrzebne cytowanie], więc np. Jeśli zmienna została przekazana, nie jest to możliwe symulować przypisanie do tej zmiennej w zakresie osoby dzwoniącej. Ponieważ jednak funkcja ma dostęp do tego samego obiektu co obiekt wywołujący (nie jest wykonywana kopia), mutacje tych obiektów, jeśli obiekty są zmienne, wewnątrz funkcji są widoczne dla obiektu wywołującego, który może wydawać się różnić od wywołania pod względem wartości semantyka. Mutacje obiektu podlegającego mutacji w ramach funkcji są widoczne dla obiektu wywołującego, ponieważ obiekt nie jest kopiowany ani klonowany - jest udostępniany.

Oznacza to, że odniesienia 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 analizie i będzie niedostępne dla funkcji wywołującej.


Nie, to, czy obiekt można modyfikować, czy nie, nie jest tak naprawdę problemem. Wszystko zawsze przechodzi przez wartość. To zależy tylko od tego, co przekazujesz (wartość lub referencja). Zobacz to .
Scott Marcus,

To, co opisuje, przekazuje referencję BY-VALUE. Nie ma powodu do wprowadzania nowej terminologii.
Sanjeev

1

Przeważnie najprostszy sposób

// Copy JS object without reference
var first = {"a":"value1","b":"value2"};
var clone = JSON.parse( JSON.stringify( first ) ); 

var second = ["a","b","c"];
var clone = JSON.parse( JSON.stringify( second ) ); 

JSON.parse( JSON.stringify( obj ) )to okropny sposób na głębokie klonowanie obiektów. Jest nie tylko powolny, ale może również powodować utratę danych.
D. Pardal

0

Znalazłem sposób przedłużyć z biblioteki underscore.js bardzo przydatna, gdy chcę przejść do obiektu jako parametr, który może być albo całkowicie zmodyfikowany lub zastąpiony.

function replaceOrModify(aObj) {
  if (modify) {

    aObj.setNewValue('foo');

  } else {

   var newObj = new MyObject();
   // _.extend(destination, *sources) 
   _.extend(newObj, aObj);
  }
}

0

Najbardziej zwięzłe wyjaśnienie, jakie znalazłem, to przewodnik po stylu AirBNB :

  • Prymitywy : kiedy uzyskujesz dostęp do typu prymitywnego, 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 : Gdy 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

Tj. Typy pierwotne są przekazywane przez wartość, a typy złożone są przekazywane przez odwołanie.


Nie, wszystko zawsze przekazuje wartość. To zależy tylko od tego, co przekazujesz (wartość lub referencja). Zobacz to .
Scott Marcus,

-1

Powiedziałbym, że jest to kopia po kopii -

Weź pod uwagę, że argumenty i obiekty zmienne są obiektami utworzonymi w kontekście wykonania utworzonym na początku wywołania funkcji - a twoja rzeczywista wartość / referencja przekazana do funkcji jest po prostu przechowywana w tych argumentach + obiektach zmiennych.

Mówiąc najprościej, dla typów pierwotnych wartości są kopiowane na początku wywołania funkcji, dla typu obiektu kopiowane są referencje.


1
„pass-by-copy” === przekazuj według wartości
Scott Marcus

-1

Trwa dyskusja na temat użycia terminu „pass by reference” w JavaScript tutaj , ale aby odpowiedzieć na twoje pytanie:

Obiekt jest automatycznie przekazywany przez odniesienie, bez konieczności jego konkretnego określania

(Z artykułu wspomnianego powyżej.)


7
Artykuł, do którego prowadzi link, nie zawiera już tych oświadczeń i całkowicie unika używania „przekazywania przez odniesienie”.
C Perkins,

Wartość jest odniesieniem

-2

Łatwym sposobem ustalenia, czy coś jest „przekazywane przez referencję”, jest to, czy można napisać funkcję „zamiany”. Na przykład w C możesz wykonać:

void swap(int *i, int *j)
{
    int t;
    t = *i;
    *i = *j;
    *j = t;
}

Jeśli nie możesz zrobić tego w JavaScript, to nie jest to „pass by reference”.


21
To tak naprawdę nie jest przekazywane przez odniesienie. Przekazujesz wskaźniki do funkcji, a wskaźniki te są przekazywane według wartości. Lepszym przykładem może być C ++ i operator lub słowo kluczowe „ref” C #, oba są naprawdę przekazywane przez odwołanie.
Matt Greer,

Jeszcze łatwiejsze jest to, że wszystko jest przekazywane przez JavaScript w wartości.
Scott Marcus,


-3
  1. zmienna typu pierwotnego jak ciąg znaków, liczby są zawsze przekazywane jako wartość przekazywana.
  2. Tablica i obiekt są przekazywane jako referencja lub wartość w oparciu o te dwa warunki.

    • jeśli zmieniasz wartość tego obiektu lub tablicy za pomocą nowego obiektu lub tablicy, to jest ona przekazywana przez wartość.

      object1 = {item: "car"}; array1=[1,2,3];

    tutaj przypisujesz nowy obiekt lub tablicę do starego. nie zmieniasz wartości właściwości starego obiektu. więc jest przekazywana przez wartość.

    • jeśli zmieniasz wartość właściwości obiektu lub tablicy, jest ona przekazywana przez odwołanie.

      object1.key1= "car"; array1[0]=9;

    tutaj zmieniasz wartość właściwości starego obiektu. nie przypisujesz nowego obiektu ani tablicy do starego. więc jest przekazywany przez referencję.

Kod

    function passVar(object1, object2, number1) {

        object1.key1= "laptop";
        object2 = {
            key2: "computer"
        };
        number1 = number1 + 1;
    }

    var object1 = {
        key1: "car"
    };
    var object2 = {
        key2: "bike"
    };
    var number1 = 10;

    passVar(object1, object2, number1);
    console.log(object1.key1);
    console.log(object2.key2);
    console.log(number1);

Output: -
    laptop
    bike
    10

1
Operatora przypisania nie należy mylić z wywołaniem funkcji. Po przypisaniu nowych danych do istniejącej zmiennej liczba referencyjna starych danych zmniejsza się, a nowe dane są powiązane ze starą zmienną. Zasadniczo zmienna wskazuje na nowe dane. To samo dotyczy zmiennych właściwości. Ponieważ te przypisania nie są wywołaniami funkcji, nie mają one nic wspólnego z przekazywaniem wartości lub przekazywaniem referencji.
John Sonderson

1
Nie, wszystko zawsze przekazuje wartość. To zależy tylko od tego, co przekazujesz (wartość lub referencja). Zobacz to .
Scott Marcus,

-3
  1. Prymitywy (liczba, wartość logiczna itp.) Są przekazywane przez wartość.
    • Ciągi są niezmienne, więc nie ma to dla nich znaczenia.
  2. Obiekty są przekazywane przez referencję (referencja jest przekazywana przez wartość).

Nie, wszystko zawsze przekazuje wartość. To zależy tylko od tego, co przekazujesz (wartość lub referencja). Zobacz to .
Scott Marcus,

Twoje drugie stwierdzenie jest samo w sobie sprzeczne.
Jörg W Mittag

-5

Proste wartości w funkcjach nie zmienią tych wartości poza funkcją (są przekazywane przez wartość), podczas gdy wartości złożone - (są przekazywane przez odwołanie).

function willNotChange(x) {

    x = 1;
}

var x = 1000;

willNotChange(x);

document.write('After function call, x = ' + x + '<br>'); // Still 1000

function willChange(y) {

    y.num = 2;
}

var y = {num: 2000};

willChange(y);
document.write('After function call y.num = ' + y.num + '<br>'); // Now 2, not 2000

to niedorzeczne, zmieni się z powodu zakresu funkcjonalnego zakresu, zostanie podniesiony nie dlatego, że jest przekazywany przez referencję.
Parijat Kalia,

Nie, wszystko zawsze przekazuje wartość. To zależy tylko od tego, co przekazujesz (wartość lub referencja). Zobacz to .
Scott Marcus,
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.