Можно ли изменить значения массива при выполнении 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]
Джастин

Ответы:


469

Обратному вызову передается элемент, индекс и сам массив.

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

edit - как отмечено в комментарии, .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()и большинством других методов итерации массива.
Заостренный

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

3
Чтобы использовать thisпереданный в качестве второго аргумента для .forEach(), необходимо передать функцию обратного вызова с использованием синтаксиса function(), поскольку использование функции стрелки ES6 () => {}не связывает контекст.
19

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это массив строк, это массив примитивных объектов, что означает, что они хранятся по значению.

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

1
Отличное объяснение. Спасибо. В конце концов я не могу получить это утверждение: как In functions, parameters are always passed by value. насчет второго примера?
Алекс

@ 7sides, Это утверждение описывает, что параметры функции всегда будут передаваться по значению. Таким образом, для примитивов это будет значение, на которое указывает примитив. Для объектов это будет местоположение, на которое указывает объект. эта страница w3schools имеет хорошее объяснение. Смотрите разделы. Аргументы передаются по значению, а объекты передаются по ссылке .
Дейв

В функциях параметры всегда передаются по значению. БАМ. Спасибо. +1
радаробоб

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
@ Берги: Нет, это не имеет значения, тип. Все значения копируются при присваивании - единственными значениями в 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 вы можете изменять содержимое Array, но по сравнению с базовым циклом for эти методы лишены одной важной функциональности. Вы не можете изменить индекс на ходу.

Например, если вы удалите текущий элемент и поместите его в другую позицию индекса в том же массиве, вы можете легко это сделать. Если вы переместите текущий элемент на предыдущую позицию, в следующей итерации проблем не будет, вы получите тот же следующий элемент, как если бы вы ничего не делали.

Рассмотрим этот код, в котором мы перемещаем элемент в позиции индекса 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 ожидается, что значение индекса будет уменьшаться при перемещении элемента массива вперед, чтобы ваш индекс оставался на той же позиции в следующем цикле, и вы все равно сможете оценить смещение элемента на место удаленного элемента. Это невозможно с помощью методов массива. Вы не можете изменить индекс. Проверьте следующий код

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

Примечание: если реализованы вставки 'before' и 'after', код должен обрабатывать вставки 'before' первым, иначе все будет не так, как ожидалось


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.