чи можна змінювати значення масиву, виконуючи foreach в javascript?


300

приклад:

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

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

alert(arr);

У масиві все ще є його оригінальні значення, чи є доступ до запису елементів масиву за допомогою функції ітерації?



Спробуйте карту ( developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… ):x=[2,3,4]; x=x.map(n=>n*2); // [4,6,8]
Джастін

Відповіді:


468

Зворотний виклик передається елементом, індексом і самим масивом.

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

редагувати - як зазначено в коментарі, .forEach()функція може приймати другий аргумент, який буде використовуватися як значення thisкожного виклику до зворотного дзвінка:

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

Цей другий приклад показує arr, що він налаштований як thisу зворотному .forEach()дзвінку. Один може подумати, що масив, включений у виклик, може бути значенням за замовчуваннямthis , але з будь-якої причини це не так; thisбуде, undefinedякщо цей другий аргумент не наводиться.

(Примітка. Вищенаведені відомості про те thisне застосовуються, якщо зворотний виклик є =>функцією, оскільки thisніколи нічого не пов'язаний, коли такі функції викликаються.)

Також важливо пам’ятати, що в прототипі Array передбачено ціле сімейство подібних утиліт, і в Stackoverflow виникає багато питань про ту чи іншу функцію, що найкращим рішенням буде просто вибрати інший інструмент. У вас є:

  • forEach для того, щоб робити речі з кожним записом у масиві або з ним;
  • filter для створення нового масиву, що містить лише кваліфіковані записи;
  • map створення нового масиву один на один, трансформуючи існуючий масив;
  • some перевірити, чи принаймні один елемент у масиві відповідає якомусь опису;
  • everyперевірити, чи всі записи в масиві відповідають опису;
  • find шукати значення в масиві

і так далі. MDN-посилання


34
Дякую! ES6: arr.forEach ((o, i, a) => a [i] = myNewVal)
DiegoRBaquero

чому part(або навіть oв es6) не визначено? як отримати ітераційне значення?
Даніїл Машкін

@DaniilMashkin partбуло б, undefinedякби елемент масиву був призначений undefinedявно. "Порожні" слоти масиву (запис масиву, якому ніколи не було призначено жодне значення), пропускаються .forEach()більшістю інших методів ітерації масиву.
Pointy

2
Для повноти .forEach()також беремо другий аргумент thisArg, який можна використовувати як thisвсередині зворотного дзвінка. ПРИМІТКА: це аргумент .forEachНЕ аргумент зворотного дзвінка.
Моха всемогутній верблюд

3
Щоб використовувати thisпереданий як другий аргумент .forEach(), вам потрібно передати функцію зворотного дзвінка за допомогою синтаксису function(), оскільки використання функції стрілки ES6 () => {}не пов'язує контекст.
jpenna

131

Давайте спробуємо зробити його простим і обговорити , як це насправді працює. Це стосується змінних типів та параметрів функції.

Ось ваш код, про який ми говоримо:

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

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

alert(arr);

По-перше, тут ви маєте читати про Array.prototype.forEach ():

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

По-друге, поговоримо коротко про типи значень у JavaScript.

Примітиви (undefined, null, String, Boolean, Number) зберігають фактичне значення.

колишній: var x = 5;

Типи довідок (спеціальні об'єкти) зберігають місце в пам'яті об'єкта.

колишній: var xObj = { x : 5 };

І третє, як працюють параметри функції.

У функціях параметри завжди передаються за значенням.

Оскільки arrце масив Strings, це масив примітивних об'єктів, це означає, що вони зберігаються за значенням.

Отже, для вашого коду вище, це означає, що кожен раз, коли ітерація forEach (), partдорівнює одному і тому ж значенню arr[index], але не одному і тому ж об'єкту .

part = "four";змінить partзмінну, але залишить у arrспокої.

Наступний код змінить потрібні вам значення:

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

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

alert(arr);

Тепер, якщо масив arrбув масивом еталонних типів , наступний код буде працювати, оскільки типи посилань зберігають місце пам'яті об'єкта замість фактичного об'єкта.

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

Далі показано, що ви можете змінити, partщоб вказати на новий об’єкт, залишаючи об’єкти, що зберігаються в самому arr:

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
дійсно, чудове пояснення! Було б навіть краще розширити пояснення щодо інших методів ітерації, для ... карти, карти, ... Нове для ... робіт дуже схожим чином, ніж для Кожного для таких випадків, за винятком того, що " індекс "відсутній для зміни зрештою початкового масиву (як у forEach). Також карта ES5 схожа на forEach, можливо, лише те, що можливість повернення значень з карти робить речі чіткішими з точки зору синтаксису, ніж використання індексу.
Крістоф Відал

1
Чудове пояснення. Дякую. Зрештою, я не можу отримати таке твердження: а як In functions, parameters are always passed by value. щодо другого прикладу?
Олексій

@ 7sides, це твердження описує, що параметри функції завжди передаються за значенням. Тож для примітивів це буде значення, на яке вказує примітив. Для об’єктів це буде місце, на яке об’єкт вказує. ця сторінка w3schools має гарне пояснення. Див. Розділи Аргументи передані за значенням, а об'єкти - посиланням .
Дейв

У функціях параметри завжди передаються за значенням. БАМ. Дякую. +1
radarbob

92

Масив: [1, 2, 3, 4]
Результат:["foo1", "foo2", "foo3", "foo4"]

Array.prototype.map() Зберігати оригінальний масив

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() Перезазначити вихідний масив

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

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


3
"Array.prototype.map () Зміна оригінального масиву, досягнутого переназначенням змінної arr" .map () ніколи не змінює вихідний масив, він створює новий. Перепризначення змінної не змінює вихідний об'єкт.
vadkou

1
let arr1 = ["1", 2, 3, 4]; arr1.map(function(v) { return "foo"+ v; }); console.log( arr ); Array.prototype.map () Ніколи не змінюйте оригінальний масив для кожного змінення.
Анупам Маурія

@AnupamMaurya Це зовсім не так. mapнапевно може мутувати свій масив і forEach, взагалі, ні.
user4642212

@SebastianSimon, дякую за відповідь. Все, що я хочу додати, forEach переосмислює дані, але карта не може, це створює нову копію.
Анупам Маурія

14

Javascript це передати за значенням, і який по суті означає , що partце копія значення в масиві.

Щоб змінити значення, перейдіть до самого масиву у своєму циклі.

arr[index] = 'new value';


Це залежить від типу partкопіювання, хоча ви маєте рацію, що змінна - це не вказівник, а значення
Bergi

8
Скажіть, що "JavaScript передається за значенням" - це грубе узагальнення. Існують типи довідок у JavaScript. Типи значень передаються за значенням.
Алекс Турпін

9
Не грубе узагальнення. Цілком правильне і абсолютне твердження. Javascript передає посилання за значенням. Javascript завжди передається за значенням.
hvgotcodes

1
@Bergi: Ні, це не має значення. Усі значення копіюються при призначенні - єдині значення в JavaScript - це примітиви та посилання. І примітиви, і посилання копіюються при дорученні.
newacct

6
@ Bergi тільки тому, що в мові є річ, яка називається посиланням, не означає, що вона використовує пропуск через посилання. Посилання можуть (і передаються) передаватися за значенням, тобто значення посилання копіюється і ця копія використовується як аргумент.
hvgotcodes


4

Ось аналогічна відповідь за допомогою =>функції стилю:

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

дає результат:

[11,12,13,14]

selfПараметр не є строго необхідним з функцією стрілки стилю, так

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

також працює.


3

Функція .forEach може мати функцію зворотного виклику (eachelement, elementIndex). В основному, що вам потрібно зробити:

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

Або якщо ви хочете зберегти початковий масив, ви можете зробити його копію, перш ніж зробити вищевказаний процес. Щоб зробити копію, ви можете використовувати:

var copy = arr.slice();

3
Якщо ви хочете зробити копію масиву, використовуйте map()замість forEach(). map()ітераціює над вихідним масивом і повертає новий масив, що містить [модифіковану] копію оригіналу: вихідний масив залишається незмінним.
Ніколас Кері

2

За допомогою об'єктних методів Array ви можете змінити вміст масиву ще порівняно з базовим для циклів, ці методи не мають однієї важливої ​​функціональності. Ви не можете змінювати індекс під час пробігу.

Наприклад, якщо ви видалите поточний елемент і помістите його в іншу позицію індексу в тому ж масиві, ви можете це легко зробити. Якщо ви перемістите поточний елемент у попереднє положення, у наступній ітерації не виникає проблем, ви отримаєте такий самий наступний елемент, як ніби нічого не зробили.

Розглянемо цей код, коли ми переміщуємо елемент у позиції індексу 5 до позиції індексу 2, коли індекс налічує до 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

Однак якщо ми перемістимо поточний елемент кудись за межі поточної позиції індексу, дещо стає безладним. Тоді вже наступний елемент зміститься у положення переміщених елементів, і в наступній ітерації ми не зможемо його побачити чи оцінити.

Розглянемо цей код, коли ми переміщуємо елемент у позиції індексу 5 до позиції індексу 7, коли індекс налічує до 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

Таким чином, ми ніколи не зустрічали 6 в циклі. Зазвичай у циклі for for ви очікуєте зменшення значення індексу, коли ви переміщуєте елемент масиву вперед, щоб ваш індекс залишався на тому ж самому положенні в наступному запуску, і ви все ще можете оцінити елемент, переміщений на місце видаленого елемента. Це неможливо з методами масиву. Ви не можете змінити індекс. Перевірте наступний код

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

Як ви бачите, коли ми декрементуємо, iвін продовжиться не з 5, а з 6, звідки він був залишений.

Тому майте це на увазі.


1

Щоб додати або видалити цілі елементи, які могли б змінити індекс, шляхом розширення пропозиції zhujy_8833 пропозиції slice () для повторення копії, просто порахуйте кількість елементів, які ви вже видалили або додали, та змініть індекс відповідно. Наприклад, для видалення елементів:

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' ]

Щоб вставити елементи раніше:

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' ]

Щоб вставити елементи після:

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']

Щоб замінити елемент:

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

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

Примітка. Якщо реалізація вставок як "до", так і "після", код повинен обробити "перед" вставками спочатку, навпаки не було б так, як очікувалося


0

Ви можете спробувати це, якщо хочете переопрацювати

var newArray= [444,555,666];
var oldArray =[11,22,33];
oldArray.forEach((name, index) => oldArray [index] = newArray[index]);
console.log(newArray);
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.