Comment supprimer un élément du tableau dans la boucle forEach?

98

J'essaie de supprimer un élément d'un tableau dans une forEachboucle, mais j'ai des problèmes avec les solutions standard que j'ai vues.

C'est ce que j'essaye actuellement:

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

Je sais qu'il entre dans le ifparce que je vois YippeeeeeE!!!!!!!!!!!!!dans la console.

MON PROBLÈME: Je sais que ma boucle for et si la logique sont saines, mais ma tentative de supprimer l'élément actuel du tableau échoue.

METTRE À JOUR:

J'ai essayé la réponse de Xotic750 et l'élément n'est toujours pas supprimé:

Voici la fonction dans mon code:

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

Voici la sortie où le tableau n'est toujours pas supprimé:

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

Donc, évidemment, il entre dans l'instruction if comme indiqué, mais il est également évident que le [• • •] est toujours là.

novicePrgrmr
la source
8
Y a-t-il une raison pour laquelle vous utilisez forEach? Si vous souhaitez supprimer des éléments, la fonction la plus appropriée est filter.
Jon
2
Pas si vous devez conserver la référence au tableau d'origine.
Xotic750
Oui, nous souhaitons conserver la référence au tableau d'origine.
novicePrgrmr
Votre question ne montre pas clairement quel est le problème réel que vous rencontrez? Pouvez-vous donner un exemple, peut-être un jsFiddle? Il semble que vous devriez peut-être utiliser l' indexattribut plutôt que itempour votresplice
Xotic750
@ Xotic750 Désolé, a ajouté une précision.
novicePrgrmr

Réponses:

236

On dirait que vous essayez de faire ça?

Itérer et muter un tableau à l'aide de 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>

Ce qui fonctionne bien pour un cas simple où vous n'avez pas 2 des mêmes valeurs que les éléments de tableau adjacents, sinon vous avez ce problème.

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>

Alors, que pouvons-nous faire pour résoudre ce problème lors de l'itération et de la mutation d'un tableau? Eh bien, la solution habituelle est de travailler en sens inverse. Utiliser ES3 pendant mais vous pouvez l'utiliser pour le sucre si vous préférez

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>

Ok, mais vous vouliez utiliser les méthodes d'itération ES5. Eh bien, l'option serait d'utiliser Array.prototype.filter mais cela ne mute pas le tableau d'origine mais en crée un nouveau, donc même si vous pouvez obtenir la bonne réponse, ce n'est pas ce que vous semblez avoir spécifié.

Nous pourrions également utiliser ES5 Array.prototype.reduceRight , pas pour sa propriété réductrice par sa propriété d'itération, c'est-à-dire itérer en sens inverse.

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>

Ou nous pourrions utiliser ES5 Array.protoype.indexOf comme ça.

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>

Mais vous souhaitez spécifiquement utiliser ES5 Array.prototype.forEach , alors que pouvons-nous faire? Eh bien, nous devons utiliser Array.prototype.slice pour faire une copie superficielle du tableau et Array.prototype.reverse afin que nous puissions travailler en sens inverse pour muter le tableau d'origine.

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>

Enfin, ES6 nous offre d'autres alternatives, où nous n'avons pas besoin de faire des copies superficielles et de les inverser. Notamment, nous pouvons utiliser des générateurs et des itérateurs . Cependant, le soutien est actuellement assez faible.

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>

Quelque chose à noter dans tout ce qui précède est que, si vous supprimiez NaN du tableau, la comparaison avec des égaux ne fonctionnera pas car en JavascriptNaN === NaN est faux. Mais nous allons ignorer cela dans les solutions car il s'agit d'un autre cas de bord non spécifié.

Alors voilà, une réponse plus complète avec des solutions qui ont encore des cas extrêmes. Le tout premier exemple de code est toujours correct, mais comme indiqué, ce n'est pas sans problèmes.

Xotic750
la source
Merci de répondre. J'ai essayé d'utiliser votre solution, mais cela ne supprime toujours pas l'élément du tableau. Je vais mettre les détails dans la question.
novicePrgrmr
Mettez console.log(review);après le forEach, comme dans mon exemple.
Xotic750
4
Attention, cela casse si deux éléments consécutifs à supprimer: var review = ['a', 'a', 'c', 'b', 'a']; donnera ['a', 'c', 'b']
quentinadam
4
REMARQUE - Cette réponse est Fausse! Le foreach parcourt le tableau par index. Une fois que vous supprimez des éléments lors de l'itération, l'index des éléments suivants change. Dans cet exemple, une fois que vous supprimez le premier «a», le numéro d'index 1 devient maintenant «c». Par conséquent, le premier «b» n'est même pas évalué. Puisque vous n'avez pas essayé de le supprimer, il s'est avéré que tout allait bien, mais ce n'est pas le cas. Vous devez parcourir une copie inversée du tableau, puis supprimer les éléments du tableau d'origine.
danbars
4
@ Xotic750 - la réponse d'origine (étant maintenant le premier extrait de code) est fausse simplement parce que forEach ne parcourra pas tous les éléments du tableau, comme je l'ai expliqué dans le commentaire précédent. Je sais que la question était de savoir comment supprimer des éléments dans une boucle forEach, mais la réponse simple est que vous ne le faites pas. Étant donné que de nombreuses personnes lisent ces réponses et copient souvent les réponses à l'aveugle (en particulier les réponses acceptées), il est important de noter les failles du code. Je pense que la boucle while inversée est la solution la plus simple, la plus efficace et la plus lisible et devrait être la réponse acceptée
danbars
37

Utilisez à la Array.prototype.filterplace de 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);
alemjerus
la source
11

Bien que la réponse de Xotic750 fournisse plusieurs bons points et des solutions possibles, la simplicité est parfois préférable .

Vous savez que le tableau en cours d'itération est en cours de mutation dans l'itération elle-même (c'est-à-dire en supprimant un élément => les changements d'index), donc la logique la plus simple est de revenir en arrière à l'ancienne for(à la langue 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());

Si vous y réfléchissez vraiment, a forEachn'est que du sucre syntaxique pour une forboucle ... Donc, si cela ne vous aide pas, arrêtez de vous casser la tête contre cela.

CPHPython
la source
1

Vous pouvez également utiliser indexOf à la place pour ce faire

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

J'ai compris que vous souhaitiez supprimer du tableau en utilisant une condition et avoir un autre tableau dont les éléments ont été supprimés du tableau. Est correct?

Que dis-tu de ça?

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

J'espère que cette aide ...

Au fait, j'ai comparé «for-loop» à «forEach».

Si remove au cas où une chaîne contient «f», le résultat est différent.

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"]
 */

Et supprimer à chaque itération, aussi un résultat est différent.

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

Parc Jun-Hong
la source
0

Ce qui suit vous donnera tous les éléments qui ne sont pas égaux à vos caractères spéciaux!

review = jQuery.grep( review, function ( value ) {
    return ( value !== '\u2022 \u2022 \u2022' );
} );
Feuille de Jenna
la source
0

Voici comment procéder:

review.forEach(function(p,index,object){
   if(review[index] === '\u2022 \u2022 \u2022'){
      console.log('YippeeeE!!!!!!!!!!!!!!!!')
      review.splice(index, 1);
   }
});
Alaeddin Hussein
la source
1
Je ne pense pas que ce soit le cas. J'ai changé mon code en supposant que p était un index, et maintenant il n'entre même plus dans l' ifinstruction.
novicePrgrmr
3
@WhoCares Vous devriez voir la spécification ecma-international.org/ecma-262/5.1/#sec-15.4.4.18 Les arguments de la fonction callBack sontitem, index, object
Xotic750