Как удалить элемент из массива в цикле forEach?


99

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

Вот что я сейчас пытаюсь:

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

Я знаю, что это происходит, ifпотому что я вижу YippeeeeeE!!!!!!!!!!!!!в консоли.

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

ОБНОВИТЬ:

Пробовал ответ 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

Ответы:


237

Похоже, вы пытаетесь это сделать?

Итерировать и изменять массив с помощью 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>

Что отлично работает для простого случая, когда у вас нет двух одинаковых значений, как у соседних элементов массива, иначе у вас есть эта проблема.

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.indexOf вот так.

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это false. Но мы собираемся проигнорировать это в решениях, поскольку это еще один неуказанный крайний случай.

Итак, у нас есть более полный ответ с решениями, которые все еще имеют крайние случаи. Самый первый пример кода все еще верен, но, как уже говорилось, не без проблем.


Спасибо за ответы. Я пробовал использовать ваше решение, но оно все еще не удаляет элемент из массива. Подробности изложу в вопросе.
novicePrgrmr

Ставьте console.log(review);после forEach, как в моем примере.
Xotic750

4
Осторожно, это сломается, если удалить два последовательных элемента: var review = ['a', 'a', 'c', 'b', 'a']; даст ['a', 'c', 'b']
quentinadam

4
ПРИМЕЧАНИЕ. - Этот ответ НЕПРАВИЛЬНЫЙ! Foreach выполняет итерацию по массиву по индексу. После удаления элементов во время итерации изменяется индекс следующих элементов. В этом примере, как только вы удалите первую букву «а», индекс номер 1 теперь станет «с». Следовательно, первая буква «b» даже не оценивается. Так как вы не пытались удалить его, просто так получилось, но это не так. Вы должны пройти через обратную копию массива, а затем удалить элементы в исходном массиве.
данбарс

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

11

Хотя ответ Xotic750 предлагает несколько хороших моментов и возможных решений, иногда лучше просто .

Вы знаете, что итерируемый массив мутирует в самой итерации (т.е. удаляет элемент => изменяется индекс), поэтому простейшая логика - вернуться назад на старомодном языке for(а-ля 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 Аргументы функции item, index, object
callBack
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.