Як видалити елемент з масиву в циклі forEach?


97

Я намагаюся видалити елемент з масиву в forEachциклі, але у мене виникають проблеми зі стандартними рішеннями, які я бачив.

Це те, що я зараз намагаюся:

review.forEach(function(p){
   if(p === '\u2022 \u2022 \u2022'){
      console.log('YippeeeE!!!!!!!!!!!!!!!!')
      review.splice(p, 1);
   }
});

Я знаю, що це потрапляє ifчерез те, що я бачу YippeeeeeE!!!!!!!!!!!!!в консолі.

МОЯ ПРОБЛЕМА: Я знаю, що мій цикл for і якщо логіка правильна, але моя спроба видалити поточний елемент з масиву не вдається.

ОНОВЛЕННЯ:

Випробував відповідь Xotic750, і елемент все ще не видаляється:

Ось функція в моєму коді:

review.forEach(function (item, index, object) {
    if (item === '\u2022 \u2022 \u2022') {
       console.log('YippeeeE!!!!!!!!!!!!!!!!')
       object.splice(index, 1);
    }
    console.log('[' + item + ']');
});

Ось результат, де масив все ще не видаляється:

[Scott McNeil]
[reviewed 4 months ago]
[ Mitsubishi is AMAZING!!!]
YippeeeE!!!!!!!!!!!!!!!!
[•  •]

Тож очевидно, що йдеться у операторі if за вказівкою, але також очевидно, що [• • •] все ще є.


8
Чи є причина, яку ви використовуєте forEach? Якщо ви хочете видалити предмети, найбільш відповідна функція filter.
Джон

2
Ні, якщо вам потрібно зберегти посилання на початковий масив.
Xotic750

Так, ми хотіли б зберегти посилання на початковий масив.
novicePrgrmr

З вашого запитання не зрозуміло, яка насправді проблема? Чи можете ви навести приклад, можливо, jsFiddle? Здається, що ви, можливо, скористаєтеся indexатрибутом, а не itemдля свогоsplice
Xotic750

@ Xotic750 Вибачте, додано роз'яснення.
novicePrgrmr

Відповіді:


235

Схоже, ви намагаєтесь це зробити?

Ітерація та мутація масиву за допомогою Array.prototype.splice

var pre = document.getElementById('out');

function log(result) {
  pre.appendChild(document.createTextNode(result + '\n'));
}

var review = ['a', 'b', 'c', 'b', 'a'];

review.forEach(function(item, index, object) {
  if (item === 'a') {
    object.splice(index, 1);
  }
});

log(review);
<pre id="out"></pre>

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

var pre = document.getElementById('out');

function log(result) {
  pre.appendChild(document.createTextNode(result + '\n'));
}

var review = ['a', 'a', 'b', 'c', 'b', 'a', 'a'];

review.forEach(function(item, index, object) {
  if (item === 'a') {
    object.splice(index, 1);
  }
});

log(review);
<pre id="out"></pre>

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

var pre = document.getElementById('out');

function log(result) {
  pre.appendChild(document.createTextNode(result + '\n'));
}

var review = ['a' ,'a', 'b', 'c', 'b', 'a', 'a'],
  index = review.length - 1;

while (index >= 0) {
  if (review[index] === 'a') {
    review.splice(index, 1);
  }

  index -= 1;
}

log(review);
<pre id="out"></pre>

Гаразд, але ви хотіли використовувати методи ітерації ES5. Добре і варіантом було б використовувати Array.prototype.filter, але це не мутує вихідний масив, а створює новий, тому, хоча ви можете отримати правильну відповідь, це не те, що, здається, ви вказали.

Ми також могли б використовувати ES5 Array.prototype.reduceRight , а не для зменшення властивості, скоріше, його властивості ітерації, тобто ітерації в зворотному порядку.

var pre = document.getElementById('out');

function log(result) {
  pre.appendChild(document.createTextNode(result + '\n'));
}

var review = ['a', 'a', 'b', 'c', 'b', 'a', 'a'];

review.reduceRight(function(acc, item, index, object) {
  if (item === 'a') {
    object.splice(index, 1);
  }
}, []);

log(review);
<pre id="out"></pre>

Або ми можемо використовувати ES5 Array.protoype.indexOяк так.

var pre = document.getElementById('out');

function log(result) {
  pre.appendChild(document.createTextNode(result + '\n'));
}

var review = ['a', 'a', 'b', 'c', 'b', 'a', 'a'],
  index = review.indexOf('a');

while (index !== -1) {
  review.splice(index, 1);
  index = review.indexOf('a');
}

log(review);
<pre id="out"></pre>

Але ви спеціально хочете використовувати ES5 Array.prototype.forEach , і що ми можемо зробити? Ну, нам потрібно використовувати Array.prototype.slice, щоб зробити дрібну копію масиву і Array.prototype.reverse, щоб ми могли працювати в зворотному напрямку, щоб мутувати вихідний масив.

var pre = document.getElementById('out');

function log(result) {
  pre.appendChild(document.createTextNode(result + '\n'));
}

var review = ['a', 'a', 'b', 'c', 'b', 'a', 'a'];

review.slice().reverse().forEach(function(item, index, object) {
  if (item === 'a') {
    review.splice(object.length - 1 - index, 1);
  }
});

log(review);
<pre id="out"></pre>

Нарешті, ES6 пропонує нам ще кілька альтернатив, де нам не потрібно робити дрібні копії та реверсувати їх. Зокрема, ми можемо використовувати Генератори та Ітератори . Однак наразі підтримка досить низька.

var pre = document.getElementById('out');

function log(result) {
  pre.appendChild(document.createTextNode(result + '\n'));
}

function* reverseKeys(arr) {
  var key = arr.length - 1;

  while (key >= 0) {
    yield key;
    key -= 1;
  }
}

var review = ['a', 'a', 'b', 'c', 'b', 'a', 'a'];

for (var index of reverseKeys(review)) {
  if (review[index] === 'a') {
    review.splice(index, 1);
  }
}

log(review);
<pre id="out"></pre>

Щось слід зазначити у всьому вищесказаному, це те, що якщо ви знімали NaN з масиву, то порівняння з рівними не буде працювати, тому що в Javascript NaN === NaNнеправдиво. Але ми будемо ігнорувати це у рішеннях, оскільки це ще один невизначений крайній випадок.

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


Дякую за відповідь. Я спробував використати ваше рішення, але воно все ще не видаляє елемент з масиву. Я викладу деталі у питанні.
novicePrgrmr

Поставте console.log(review);після forEach, як у моєму прикладі.
Xotic750

4
Обережно, це ламається, якщо два послідовних елементи для видалення: var review = ['a', 'a', 'c', 'b', 'a']; дасть ['a', 'c', 'b']
квентінадам

3
ПРИМІТКА - Ця відповідь неправильна! Foreach перебирає масив за індексом. Після видалення елементів під час ітерації змінюється індекс наступних елементів. У цьому прикладі, як тільки ви видалите перше "a", індекс 1 стає тепер "c". Тому перше "b" навіть не оцінюється. Оскільки ви не намагалися її видалити, це просто трапилося, але це не такий спосіб. Вам слід повторити через зворотну копію масиву, а потім видалити елементи з початкового масиву.
danbars

4
@ Xotic750 - оригінальна відповідь (зараз це перший фрагмент коду) помилкова просто тому, що forEach не буде проходити цикл через усі елементи масиву, як я пояснив у попередньому коментарі. Я знаю, що питання полягало в тому, як видалити елементи у циклі forEach, але проста відповідь полягає в тому, що ви цього не робите. Оскільки багато людей читають ці відповіді і багато разів сліпо копіюють відповіді (особливо прийняті відповіді), важливо відзначити недоліки коду. Я думаю, що зворотний цикл while є найпростішим, найефективнішим та найбільш читабельним рішенням, і він повинен бути прийнятою відповіддю
danbars

37

Використовуйте Array.prototype.filterзамість forEach:

var pre = document.getElementById('out');

function log(result) {
  pre.appendChild(document.createTextNode(result + '\n'));
}

var review = ['a', 'b', 'c', 'b', 'a', 'e'];
review = review.filter(item => item !== 'a');
log(review);

10

Хоча відповідь Xotic750 дає кілька хороших моментів та можливих рішень, іноді просте краще .

Ви знаєте, що масив, який повторюється, мутується в самій ітерації (тобто видалення елемента => зміни в індексі), таким чином, найпростіша логіка полягає в тому, щоб повернутися назад по-старому for(мова à la C ):

let arr = ['a', 'a', 'b', 'c', 'b', 'a', 'a'];

for (let i = arr.length - 1; i >= 0; i--) {
  if (arr[i] === 'a') {
    arr.splice(i, 1);
  }
}

document.body.append(arr.join());

Якщо ви дійсно думаєте про це, a forEach- це просто синтаксичний цукор для forпетлі ... Тож якщо це не допомагає вам, просто припиніть ламати голову проти нього.


1

Ви також можете використовувати indexOf для цього

var i = review.indexOf('\u2022 \u2022 \u2022');
if (i !== -1) review.splice(i,1);

1

Я зрозумів, що ви хочете видалити з масиву за допомогою умови і мати інший масив, який має елементи, видалені з масиву. Правильно?

Як щодо цього?

var review = ['a', 'b', 'c', 'ab', 'bc'];
var filtered = [];
for(var i=0; i < review.length;) {
  if(review[i].charAt(0) == 'a') {
    filtered.push(review.splice(i,1)[0]);
  }else{
    i++;
  }
}

console.log("review", review);
console.log("filtered", filtered);

Сподіваюся, що це допоможе ...

До речі, я порівнював «for-loop» з «forEach».

Якщо видалити, якщо рядок містить 'f', результат буде іншим.

var review = ["of", "concat", "copyWithin", "entries", "every", "fill", "filter", "find", "findIndex", "flatMap", "flatten", "forEach", "includes", "indexOf", "join", "keys", "lastIndexOf", "map", "pop", "push", "reduce", "reduceRight", "reverse", "shift", "slice", "some", "sort", "splice", "toLocaleString", "toSource", "toString", "unshift", "values"];
var filtered = [];
for(var i=0; i < review.length;) {
  if( review[i].includes('f')) {
    filtered.push(review.splice(i,1)[0]);
  }else {
    i++;
  }
}
console.log("review", review);
console.log("filtered", filtered);
/**
 * review [  "concat",  "copyWithin",  "entries",  "every",  "includes",  "join",  "keys",  "map",  "pop",  "push",  "reduce",  "reduceRight",  "reverse",  "slice",  "some",  "sort",  "splice",  "toLocaleString",  "toSource",  "toString",  "values"] 
 */

console.log("========================================================");
review = ["of", "concat", "copyWithin", "entries", "every", "fill", "filter", "find", "findIndex", "flatMap", "flatten", "forEach", "includes", "indexOf", "join", "keys", "lastIndexOf", "map", "pop", "push", "reduce", "reduceRight", "reverse", "shift", "slice", "some", "sort", "splice", "toLocaleString", "toSource", "toString", "unshift", "values"];
filtered = [];

review.forEach(function(item,i, object) {
  if( item.includes('f')) {
    filtered.push(object.splice(i,1)[0]);
  }
});

console.log("-----------------------------------------");
console.log("review", review);
console.log("filtered", filtered);

/**
 * review [  "concat",  "copyWithin",  "entries",  "every",  "filter",  "findIndex",  "flatten",  "includes",  "join",  "keys",  "map",  "pop",  "push",  "reduce",  "reduceRight",  "reverse",  "slice",  "some",  "sort",  "splice",  "toLocaleString",  "toSource",  "toString",  "values"]
 */

І видаляйте кожною ітерацією, також результат різний.

var review = ["of", "concat", "copyWithin", "entries", "every", "fill", "filter", "find", "findIndex", "flatMap", "flatten", "forEach", "includes", "indexOf", "join", "keys", "lastIndexOf", "map", "pop", "push", "reduce", "reduceRight", "reverse", "shift", "slice", "some", "sort", "splice", "toLocaleString", "toSource", "toString", "unshift", "values"];
var filtered = [];
for(var i=0; i < review.length;) {
  filtered.push(review.splice(i,1)[0]);
}
console.log("review", review);
console.log("filtered", filtered);
console.log("========================================================");
review = ["of", "concat", "copyWithin", "entries", "every", "fill", "filter", "find", "findIndex", "flatMap", "flatten", "forEach", "includes", "indexOf", "join", "keys", "lastIndexOf", "map", "pop", "push", "reduce", "reduceRight", "reverse", "shift", "slice", "some", "sort", "splice", "toLocaleString", "toSource", "toString", "unshift", "values"];
filtered = [];

review.forEach(function(item,i, object) {
  filtered.push(object.splice(i,1)[0]);
});

console.log("-----------------------------------------");
console.log("review", review);
console.log("filtered", filtered);


0

Далі подано всі елементи, які не дорівнюють вашим спеціальним символам!

review = jQuery.grep( review, function ( value ) {
    return ( value !== '\u2022 \u2022 \u2022' );
} );

0

Ось як слід це зробити:

review.forEach(function(p,index,object){
   if(review[index] === '\u2022 \u2022 \u2022'){
      console.log('YippeeeE!!!!!!!!!!!!!!!!')
      review.splice(index, 1);
   }
});

1
Я не думаю, що це так. Я змінив свій код, припустивши, що p є індексом, і тепер він навіть не потрапляє у ifзаяву.
novicePrgrmr

3
@WhoCares Ви повинні побачити специфікацію ecma-international.org/ecma-262/5.1/#sec-15.4.4.18 Аргументами функції callBack єitem, index, object
Xotic750
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.