Dlaczego zmiana tablicy w JavaScript wpływa na kopie tablicy?

83

Napisałem następujący JavaScript:

var myArray = ['a', 'b', 'c'];
var copyOfMyArray = myArray;
copyOfMyArray.splice(0, 1);
alert(myArray); // alerts ['b','c']
alert(copyOfMyArray); // alerts ['b','c']

var myNumber = 5;
var copyOfMyNumber = myNumber;
copyOfMyNumber = copyOfMyNumber - 1;
alert(myNumber); // alerts 5
alert(copyOfMyNumber); // alerts 4        

Ten kod deklaruje zmienną myArrayi ustawia ją na wartość tablicy. Następnie deklaruje drugą zmienną copyOfMyArrayi ustawia ją na myArray. Wykonuje operację na, copyOfMyArraya następnie ostrzega zarówno myArrayi copyOfMyArray. W jakiś sposób, gdy wykonuję operację na copyOfMyArray, wydaje się, że ta sama operacja jest wykonywana na myArray.

Następnie kod robi to samo z wartością liczbową: deklaruje zmienną myNumberi ustawia ją na wartość liczbową. Następnie deklaruje drugą zmienną copyOfMyNumberi ustawia ją na myNumber. Wykonuje operację na, copyOfMyNumbera następnie ostrzega zarówno myNumberi copyOfMyNumber. Tutaj otrzymuję oczekiwane zachowanie: różne wartości dla myNumberi copyOfMyNumber.

Jaka jest różnica między tablicą a liczbą w JavaScript, że wydaje się, że zmiana tablicy zmienia wartość kopii tablicy, podczas gdy zmiana liczby nie zmienia wartości kopii liczby?

Zgaduję, że z jakiegoś powodu tablica jest określana przez odniesienie, a liczba przez wartość, ale dlaczego? Skąd mogę wiedzieć, jakiego zachowania należy się spodziewać w przypadku innych obiektów?

Rzeka Vivian
źródło

Odpowiedzi:

112

Tablica w JavaScript jest również obiektem, a zmienne zawierają tylko odniesienie do obiektu, a nie sam obiekt. Zatem obie zmienne mają odniesienie do tego samego obiektu.

Przy okazji, Twoje porównanie z przykładem liczbowym nie jest poprawne. Przypisujesz nową wartość do copyOfMyNumber. Jeśli przypiszesz mu nową wartość copyOfMyArray, również się nie zmieni myArray.

Możesz utworzyć kopię tablicy za pomocą slice [docs] :

var copyOfMyArray = myArray.slice(0);

Ale pamiętaj, że zwraca to tylko płytką kopię, tj. Obiekty wewnątrz tablicy nie zostaną sklonowane.

Felix Kling
źródło
+1 - tak z ciekawości, czy jest jakaś wada przypisywania myArray.slice(0);bezpośrednio w tym kontekście?
jAndy
2
@Rice: Nie, po prostu edytuję, aby wyjaśnić. Jeśli chcesz głęboką kopię, musisz coś napisać samodzielnie. Ale jestem pewien, że znajdziesz skrypt, który to robi.
Felix Kling,
@FelixKling: Nie mam przykładu. Pytałem tylko, ponieważ najpierw zastosowałeś metodę prototypową.
jAndy
@jAndy: Ach, więc odniosłeś się do tego ... Byłem trochę zdezorientowany, a ostatnio częściej;)
Felix Kling
23

Cóż, jedyną możliwą odpowiedzią - i właściwą - jest to, że w rzeczywistości nie kopiujesz tablicy. Kiedy piszesz

var copyOfArray = array;

przypisujesz odwołanie do tej samej tablicy do innej zmiennej. Innymi słowy, oba wskazują na ten sam obiekt.

Spiczasty
źródło
Powiedziałbym, że nie przypisujesz dokładnie wskaźnika odniesienia, przypisujesz jak kopię odniesienia. Ponieważ jeśli przekażesz obj do function i spróbujesz zastąpić go innym nowym obiektem wewnątrz funkcji, nie zmienisz oryginalnego obiektu.
kashesandr
1
@kashesandr tak, „przypisanie referencji” oznacza „przypisanie kopii referencji”, to prawda. Jednak dwa równe odwołania są zawsze równe, tak jak dwa wystąpienia liczby 5są zawsze równe.
Pointy
16

Więc wszyscy tutaj wykonali świetną robotę, wyjaśniając, dlaczego tak się dzieje - chciałem tylko rzucić uwagę i dać ci znać, jak udało mi się to naprawić - całkiem łatwo:

thingArray = ['first_thing', 'second_thing', 'third_thing']
function removeFirstThingAndPreserveArray(){
  var copyOfThingArray = [...thingArray]
  copyOfThingArray.shift();
  return copyOfThingArray;
}

To jest przy użyciu składni ... spread.

Źródło składni spreadu

EDYTUJ: Jeśli chodzi o powód tego i odpowiedz na twoje pytanie:

Jaka jest różnica między tablicą a liczbą w JavaScript, że wydaje się, że zmiana tablicy zmienia wartość kopii tablicy, podczas gdy zmiana liczby nie zmienia wartości kopii liczby?

Odpowiedź jest taka, że ​​w JavaScript tablice i obiekty są zmienne , podczas gdy łańcuchy i liczby oraz inne prymitywy są niezmienne . Kiedy wykonujemy zadanie takie jak:

var myArray = ['a', 'b', 'c']; var copyOfMyArray = myArray;

copyOfMyArray to tak naprawdę tylko odniesienie do myArray, a nie rzeczywista kopia.

Polecam ten artykuł, Czym są niezmienne i zmienne struktury danych? , aby zagłębić się w temat.

Glosariusz MDN: Mutable

Salvatore
źródło
2
Dokładnie to, czego szukałem.
Fabien TheSolution
6

Klonowanie obiektów -

A loop / array.pushdaje podobny wynik do array.slice(0)lub array.clone(). Wszystkie wartości są przekazywane przez odwołanie, ale ponieważ większość pierwotnych typów danych jest niezmienna , kolejne operacje dają pożądany wynik - „klon”. Nie dotyczy to oczywiście obiektów i tablic, które pozwalają na modyfikację oryginalnego odniesienia (są to typy zmienne).

Weźmy następujący przykład:

const originalArray = [1, 'a', false, {foor: 'bar'}]
const newArray = [];

originalArray.forEach((v, i) => {
    newArray.push(originalArray[i]);
});

newArray[0] = newArray[0] + 1;
newArray[1] = 'b';
newArray[2] = true;
newArray[3] = Object.assign(newArray[3], {bar: 'foo'});

Wszystkie operacje wykonywane na indeksach newArray dają pożądany wynik, z wyjątkiem finału (obiektu), który, ponieważ jest kopiowany przez odniesienie, powoduje również mutację tablicy originalArray [3].

https://jsfiddle.net/7ajz2m6w/

Zauważ, że array.slice(0) and array.clone()cierpi na to samo ograniczenie.

Jednym ze sposobów rozwiązania tego problemu jest skuteczne klonowanie obiektu podczas sekwencji wypychania:

originalArray.forEach((v, i) => {
    const val = (typeof v === 'object') ? Object.assign({}, v) : v;
    newArray.push(val);
});

https://jsfiddle.net/e5hmnjp0/

Twoje zdrowie

Bosworth99
źródło
3

W JS operator „=” kopiuje wskaźnik do obszaru pamięci tablicy. Jeśli chcesz skopiować tablicę do innej, musisz użyć funkcji Clone.

Dla liczb całkowitych jest inny, ponieważ są typem pierwotnym.

S.

DonCallisto
źródło
1

Wszystko jest kopiowane przez odwołanie, z wyjątkiem pierwotnych typów danych (łańcuchy i liczby IIRC).

Quentin
źródło
To nieprawda. Wszystkie zadania przypisują referencje. Ciągi i liczby są niezmienne.
SLaks
1

Nie masz żadnych kopii.
Masz wiele zmiennych przechowujących tę samą tablicę.

Podobnie masz wiele zmiennych o tej samej liczbie.

Kiedy piszesz copyOfMyNumber = ..., wstawiasz nową liczbę do zmiennej.
To jak pisanie copyOfMyArray = ....

Kiedy piszesz copyOfMyArray.splice, modyfikujesz oryginalną tablicę .
Nie jest to możliwe w przypadku liczb, ponieważ liczby są niezmienne i nie można ich modyfikować,

SLaks
źródło
1

Utwórz filtr oryginalnej tablicy w arrayCopy. Więc zmiany w nowej tablicy nie wpłyną na oryginalną tablicę.

var myArray = ['a', 'b', 'c'];
var arrayCopy = myArray.filter(function(f){return f;})
arrayCopy.splice(0, 1);
alert(myArray); // alerts ['a','b','c']
alert(arrayCopy); // alerts ['b','c']

Mam nadzieję, że to pomoże.

Stos
źródło
1

Problem z płytką kopią polega na tym, że wszystkie obiekty nie są klonowane, zamiast tego otrzymują referencje, więc array.slice (0) będzie działać dobrze tylko z tablicą literałów, ale nie zrobi płytkiej kopii z tablicą obiektów. W takim przypadku jeden sposób to ...

var firstArray = [{name: 'foo', id: 121}, {name: 'zoo', id: 321}];
var clonedArray = firstArray.map((_arrayElement) => Object.assign({}, _arrayElement));  
console.log(clonedArray);
// [{name: 'foo', id: 121}, {name: 'zoo', id: 321}]  // shallow copy
Omprakash Sharma
źródło
0

Możesz dodać obsługę błędów w zależności od przypadków i użyć czegoś podobnego do poniższej funkcji, aby rozwiązać problem. Prosimy o komentarz w przypadku jakichkolwiek błędów / problemów / pomysłów dotyczących wydajności.

function CopyAnArray (ari1) {
   var mxx4 = [];
   for (var i=0;i<ari1.length;i++) {
      var nads2 = [];
      for (var j=0;j<ari1[0].length;j++) {
         nads2.push(ari1[i][j]);
      }
      mxx4.push(nads2);
   }
   return mxx4;
}
Erjim
źródło
-1

Tablica lub obiekt w javascript zawsze zawiera to samo odniesienie, chyba że sklonujesz lub skopiujesz. Oto przykład:

http://plnkr.co/edit/Bqvsiddke27w9nLwYhcl?p=preview

// for showing that objects in javascript shares the same reference

var obj = {
  "name": "a"
}

var arr = [];

//we push the same object
arr.push(obj);
arr.push(obj);

//if we change the value for one object
arr[0].name = "b";

//the other object also changes
alert(arr[1].name);

Do klonowania obiektu możemy użyć .clone () w jquery i angular.copy (), te funkcje utworzą nowy obiekt z innym odniesieniem. Jeśli znasz więcej funkcji do tego, powiedz mi, dzięki!

Albert Xu
źródło