czy można zmienić wartości tablicy podczas wykonywania foreach w javascript?


300

przykład:

var arr = ["one","two","three"];

arr.forEach(function(part){
  part = "four";
  return "four";
})

alert(arr);

Tablica nadal ma swoje oryginalne wartości, czy jest jakiś sposób na dostęp do zapisu elementów tablicy z funkcji iteracji?



Wypróbuj mapę ( developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… ):x=[2,3,4]; x=x.map(n=>n*2); // [4,6,8]
Justin

Odpowiedzi:


468

Oddzwonienie jest przekazywane element, indeks i samą tablicę.

arr.forEach(function(part, index, theArray) {
  theArray[index] = "hello world";
});

edycja - jak zauważono w komentarzu, .forEach()funkcja może przyjąć drugi argument, który będzie użyty jako wartość thisw każdym wywołaniu wywołania zwrotnego:

arr.forEach(function(part, index) {
  this[index] = "hello world";
}, arr); // use arr as this

Ten drugi przykład pokazuje arr, że konfiguruje się jak thisw wywołaniu zwrotnym. Można by pomyśleć, że tablica zaangażowana w .forEach()wywołanie może być wartością domyślnąthis , ale z jakiegokolwiek powodu tak nie jest; thisbędzie, undefinedjeśli ten drugi argument nie zostanie podany.

(Uwaga: powyższe informacje thisnie mają zastosowania, jeśli wywołanie zwrotne jest =>funkcją, ponieważ thisnigdy nie jest związane z niczym, gdy takie funkcje są wywoływane).

Ważne jest również, aby pamiętać, że na prototypie Array dostępna jest cała rodzina podobnych narzędzi, a na Stackoverflow pojawia się wiele pytań dotyczących jednej lub drugiej funkcji, tak aby najlepszym rozwiązaniem było wybranie innego narzędzia. Masz:

  • forEach za robienie rzeczy z lub do każdego wpisu w tablicy;
  • filter do tworzenia nowej tablicy zawierającej tylko kwalifikujące się wpisy;
  • map do tworzenia nowej tablicy jeden do jednego poprzez transformację istniejącej tablicy;
  • some aby sprawdzić, czy co najmniej jeden element w tablicy pasuje do jakiegoś opisu;
  • everyaby sprawdzić, czy wszystkie wpisy w tablicy pasują do opisu;
  • find szukać wartości w tablicy

i tak dalej. Link MDN


34
Dzięki! ES6: arr.forEach ((o, i, a) => a [i] = myNewVal)
DiegoRBaquero

dlaczego part(a nawet ow es6) jest niezdefiniowany? jak uzyskać iterowaną wartość?
Daniil Mashkin

@DaniilMashkin partbyłoby, undefinedgdyby element tablicy został undefinedjawnie przypisany . „Puste” pola tablicy (pozycja tablicy, której nigdy nie przypisano żadnej wartości) są pomijane przez .forEach()większość innych metod iteracji tablicy.
Pointy

2
Dla kompletności .forEach()bierze również drugi argument thisArg, którego można użyć jako thiswewnątrz wywołania zwrotnego. UWAGA: jest to argument .forEachNIE będący argumentem wywołania zwrotnego.
Moha Wszechmogący wielbłąd

3
Aby użyć thisprzekazanego jako drugiego argumentu do .forEach(), musisz przekazać funkcję wywołania zwrotnego za pomocą składni function(), ponieważ użycie funkcji strzałki ES6 () => {}nie wiąże kontekstu.
jpenna

131

Załóżmy, spróbuj zachować to proste i dyskutować, jak to rzeczywiście działa. Ma to związek z typami zmiennych i parametrami funkcji.

Oto twój kod, o którym mówimy:

var arr = ["one","two","three"];

arr.forEach(function(part) {
  part = "four";
  return "four";
})

alert(arr);

Po pierwsze, tutaj powinieneś przeczytać o Array.prototype.forEach ():

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach

Po drugie, pomówmy krótko o typach wartości w JavaScript.

Prymitywy (niezdefiniowane, null, String, Boolean, Number) przechowują rzeczywistą wartość.

dawny: var x = 5;

Typy referencyjne (obiekty niestandardowe) przechowują lokalizację pamięci obiektu.

dawny: var xObj = { x : 5 };

I po trzecie, jak działają parametry funkcji.

W funkcjach parametry są zawsze przekazywane przez wartość.

Ponieważ arrjest tablicą ciągów, jest to tablica prymitywnych obiektów, co oznacza, że ​​są one przechowywane według wartości.

Tak więc dla powyższego kodu oznacza to, że za każdym razem iteracja forEach () partjest równa tej samej wartości co arr[index], ale nie ten sam obiekt .

part = "four";zmieni partzmienną, ale pozostawi w arrspokoju.

Poniższy kod zmieni pożądane wartości:

var arr = ["one","two","three"];

arr.forEach(function(part, index) {
  arr[index] = "four";
});

alert(arr);

Teraz, jeśli tablica arrbyła tablicą typów referencji , poniższy kod będzie działał, ponieważ typy referencji przechowują lokalizację pamięci obiektu zamiast rzeczywistego obiektu.

var arr = [{ num : "one" }, { num : "two"}, { num : "three"}];

arr.forEach(function(part, index) {
  // part and arr[index] point to the same object
  // so changing the object that part points to changes the object that arr[index] points to

  part.num = "four";
});

alert(arr[0].num);
alert(arr[1].num);
alert(arr[2].num);

Poniżej pokazano, że możesz zmienić, partaby wskazywać nowy obiekt, pozostawiając obiekty przechowywane arrsame:

var arr = [{ num : "one" }, { num : "two"}, { num : "three"}];

arr.forEach(function(part, index) {
  // the following will not change the object that arr[index] points to because part now points at a new object
  part = 5;
});

alert(arr[0].num);
alert(arr[1].num);
alert(arr[2].num);

2
w rzeczy samej, świetne wyjaśnienie! Byłoby jeszcze lepiej rozszerzyć wyjaśnienie na inne metody iteracji dla ... mapy, ... Nowy dla ... prac działa w bardzo podobny sposób niż dla każdego w takich przypadkach, z wyjątkiem tego, że „ indeksu ”brakuje do ostatecznej zmiany oryginalnej tablicy (jak w forEach). Również mapa ES5 wydaje się podobna do forEach, być może po prostu możliwość zwrócenia wartości z mapy sprawia, że ​​wszystko jest wyraźniejsze z perspektywy składni niż przy użyciu indeksu.
Christophe Vidal

1
Świetne wyjaśnienie. Dziękuję Ci. W końcu nie mogę uzyskać tego stwierdzenia: In functions, parameters are always passed by value. co z drugim przykładem?
Alex

@ 7sides, Ta instrukcja opisuje, że parametry funkcji zawsze będą przekazywane przez wartość. W przypadku prymitywów będzie to wartość wskazywana przez prymityw. W przypadku obiektów będzie to lokalizacja, na którą obiekt wskazuje. ta strona w3schools ma dobre wyjaśnienie. Patrz sekcje Argumenty są przekazywane przez wartość, a obiekty są przekazywane przez odwołanie .
Dave

W funkcjach parametry są zawsze przekazywane przez wartość. BAM dzięki. +1
radarbob

92

Tablica: [1, 2, 3, 4]
Wynik:["foo1", "foo2", "foo3", "foo4"]

Array.prototype.map() Zachowaj oryginalny układ

const originalArr = ["Iron", "Super", "Ant", "Aqua"];
const modifiedArr = originalArr.map(name => `${name}man`);

console.log( "Original: %s", originalArr );
console.log( "Modified: %s", modifiedArr );

Array.prototype.forEach() Zastąp oryginalną tablicę

const originalArr = ["Iron", "Super", "Ant", "Aqua"];
originalArr.forEach((name, index) => originalArr[index] = `${name}man`);

console.log( "Overridden: %s", originalArr );


3
„Array.prototype.map () Zmodyfikuj oryginalną tablicę, osiągniętą przez ponowne przypisanie zmiennej arr” .map () nigdy nie modyfikuje oryginalnej tablicy, tworzy nową. Ponowne przypisanie zmiennej nie zmienia oryginalnego obiektu.
vadkou

1
let arr1 = ["1", 2, 3, 4]; arr1.map(function(v) { return "foo"+ v; }); console.log( arr ); Array.prototype.map () Nigdy nie modyfikuj oryginalnej tablicy, forEach mutate.
Anupam Maurya

@AnupamMaurya To wcale nie prawda. mapzdecydowanie może mutować swoją tablicę i forEachgeneralnie nie.
user4642212

@SebastianSimon, dzięki za odpowiedź. Wszystko, co chcę dodać, forEach zastępuje dane, ale mapa nie może, tworzy nową kopię.
Anupam Maurya

14

JavaScript jest przechodzi przez wartość, i która w istocie oznacza partto kopię wartości w tablicy.

Aby zmienić wartość, uzyskaj dostęp do samej tablicy w swojej pętli.

arr[index] = 'new value';


Zależy to od rodzaju tego, partczy jest kopiowane - chociaż masz rację, że zmienna nie jest wskaźnikiem, ale wartością
Bergi

8
Mówienie „JavaScript jest przekazywany po wartości” jest rażącym uogólnieniem. W JavaScript są typy referencyjne. Typy wartości są przekazywane według wartości.
Alex Turpin

9
Nie rażące uogólnienie. Całkowicie poprawne i absolutne stwierdzenie. JavaScript przekazuje referencje według wartości. JavaScript jest zawsze przekazywany przez wartość.
hvgotcodes

1
@Bergi: Nie, nie ma znaczenia rodzaj. Wszystkie wartości są kopiowane przy przypisaniu - jedynymi wartościami w JavaScript są prymitywy i referencje. Zarówno operacje pierwotne, jak i referencje są kopiowane przy przypisaniu.
newacct

6
@Bergi tylko dlatego, że w języku jest coś, co nazywa się referencją, nie oznacza to, że używa pass by referencji. Referencje mogą być (i są) przekazywane przez wartość, tj. Wartość referencji jest kopiowana i ta kopia jest używana jako argument.
hvgotcodes,


4

Oto podobna odpowiedź przy użyciu =>funkcji stylu:

var data = [1,2,3,4];
data.forEach( (item, i, self) => self[i] = item + 10 );

daje wynik:

[11,12,13,14]

Ten selfparametr nie jest konieczny w przypadku funkcji stylu strzałki, więc

data.forEach( (item,i) => data[i] = item + 10);

działa również.


3

Funkcja .forEach może mieć funkcję zwrotną (eachelement, elementIndex) Zasadniczo więc musisz:

arr.forEach(function(element,index){
    arr[index] = "four";   //set the value  
});
console.log(arr); //the array has been overwritten.

Lub jeśli chcesz zachować oryginalną tablicę, możesz wykonać jej kopię przed wykonaniem powyższego procesu. Aby wykonać kopię, możesz użyć:

var copy = arr.slice();

3
Jeśli chcesz zrobić kopię tablicy, użyj map()zamiast forEach(). map()iteruje tablicę źródłową i zwraca nową tablicę zawierającą [zmodyfikowaną] kopię oryginału: tablica źródłowa pozostaje niezmieniona.
Nicholas Carey,

2

Metodami obiektowymi Array można modyfikować zawartość Array, ale w porównaniu z podstawowymi pętlami for, metody te nie mają jednej ważnej funkcji. Nie można modyfikować indeksu w biegu.

Na przykład, jeśli usuniesz bieżący element i umieścisz go w innej pozycji indeksu w tej samej tablicy, możesz to łatwo zrobić. Jeśli przeniesiesz bieżący element do poprzedniej pozycji, w następnej iteracji nie będzie problemu, otrzymasz ten sam następny element, jakbyś nic nie zrobił.

Rozważ ten kod, w którym przesuwamy element z pozycji indeksu 5 do pozycji indeksu 2, gdy indeks policzy do 5.

var ar = [0,1,2,3,4,5,6,7,8,9];
ar.forEach((e,i,a) => {
i == 5 && a.splice(2,0,a.splice(i,1)[0])
console.log(i,e);
}); // 0 0 - 1 1 - 2 2 - 3 3 - 4 4 - 5 5 - 6 6 - 7 7 - 8 8 - 9 9

Jeśli jednak przeniesiemy bieżący element do miejsca poza bieżącą pozycją indeksu, sytuacja stanie się nieco nieuporządkowana. Następnie następny element przesunie się do pozycji przeniesionych elementów, a podczas następnej iteracji nie będziemy w stanie go zobaczyć ani ocenić.

Rozważ ten kod, w którym przesuwamy element z pozycji indeksu 5 do pozycji indeksu 7, gdy indeks policzy do 5.

var a = [0,1,2,3,4,5,6,7,8,9];
a.forEach((e,i,a) => {
i == 5 && a.splice(7,0,a.splice(i,1)[0])
console.log(i,e);
}); // 0 0 - 1 1 - 2 2 - 3 3 - 4 4 - 5 5 - 6 7 - 7 5 - 8 8 - 9 9

Więc nigdy nie spotkaliśmy 6 w pętli. Zwykle w pętli for oczekuje się zmniejszenia wartości indeksu podczas przesuwania elementu tablicy do przodu, tak aby indeks pozostał na tej samej pozycji w następnym uruchomieniu i nadal można ocenić element przesunięty na miejsce usuniętego elementu. Nie jest to możliwe w przypadku metod tablicowych. Nie możesz zmienić indeksu. Sprawdź następujący kod

var a = [0,1,2,3,4,5,6,7,8,9];
a.forEach((e,i,a) => {
i == 5 && (a.splice(7,0,a.splice(i,1)[0]), i--);
console.log(i,e);
}); // 0 0 - 1 1 - 2 2 - 3 3 - 4 4 - 4 5 - 6 7 - 7 5 - 8 8 - 9 9

Jak widać, kiedy zmniejszamy, inie będzie kontynuowane od 5, ale od 6, od miejsca, w którym zostało.

Miej to na uwadze.


1

Aby całkowicie dodać lub usunąć elementy, które zmieniłyby indeks, poprzez rozszerzenie sugestii funkcji zhujy_8833 funkcji slice () w celu iteracji po kopii, wystarczy policzyć liczbę elementów, które zostały już usunięte lub dodane i odpowiednio zmodyfikować indeks. Na przykład, aby usunąć elementy:

let values = ["A0", "A1", "A2", "A3", "A4", "A5", "A6", "A7", "A8"];
let count = 0;
values.slice().forEach((value, index) => {
    if (value === "A2" || value === "A5") {
        values.splice(index - count++, 1);
    };
});
console.log(values);

// Expected: [ 'A0', 'A1', 'A3', 'A4', 'A6', 'A7', 'A8' ]

Aby wstawić elementy wcześniej:

if (value === "A0" || value === "A6" || value === "A8") {
    values.splice(index - count--, 0, 'newVal');
};

// Expected: ['newVal', A0, 'A1', 'A2', 'A3', 'A4', 'A5', 'newVal', 'A6', 'A7', 'newVal', 'A8' ]

Aby wstawić elementy po:

if (value === "A0" || value === "A6" || value === "A8") {
    values.splice(index - --count, 0, 'newVal');
};

// Expected: ['A0', 'newVal', 'A1', 'A2', 'A3', 'A4', 'A5', 'A6', 'newVal', 'A7', 'A8', 'newVal']

Aby zastąpić element:

if (value === "A3" || value === "A4" || value === "A7") {
    values.splice(index, 1, 'newVal');
};

// Expected: [ 'A0', 'A1', 'A2', 'newVal', 'newVal', 'A5', 'A6', 'newVal', 'A8' ]

Uwaga: jeśli implementuje się zarówno wstawki „przed”, jak i „po”, kod powinien obsługiwać najpierw wstawki „przed”, w przeciwnym razie nie byłoby to zgodne z oczekiwaniami


0

Możesz spróbować, jeśli chcesz zastąpić

var newArray= [444,555,666];
var oldArray =[11,22,33];
oldArray.forEach((name, index) => oldArray [index] = newArray[index]);
console.log(newArray);
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.