Làm cách nào để xóa phần tử khỏi mảng trong vòng lặp forEach?


97

Tôi đang cố gắng xóa một phần tử trong một mảng trong forEachvòng lặp, nhưng gặp sự cố với các giải pháp tiêu chuẩn mà tôi đã thấy.

Đây là những gì tôi hiện đang thử:

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

Tôi biết nó đang đi vào ifvì tôi đang nhìn thấy YippeeeeeE!!!!!!!!!!!!!trong bảng điều khiển.

VẤN ĐỀ CỦA TÔI: Tôi biết rằng vòng lặp for và if của tôi là logic, nhưng nỗ lực xóa phần tử hiện tại khỏi mảng của tôi không thành công.

CẬP NHẬT:

Đã thử câu trả lời của Xotic750 và phần tử vẫn không bị xóa:

Đây là hàm trong mã của tôi:

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

Đây là kết quả mà mảng vẫn chưa bị xóa:

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

Vì vậy, rõ ràng là nó đang đi vào câu lệnh if như được chỉ dẫn, nhưng rõ ràng là [• • •] vẫn ở đó.


8
Có một lý do bạn đang sử dụng forEach? Nếu bạn muốn loại bỏ các mục, chức năng thích hợp nhất là filter.
Jon

2
Không nếu bạn cần giữ tham chiếu đến mảng ban đầu.
Xotic750

Có, chúng tôi muốn giữ tham chiếu đến mảng ban đầu.
novicePrgrmr

Không rõ từ câu hỏi của bạn, vấn đề thực tế mà bạn đang gặp phải là gì? Bạn có thể đưa ra một ví dụ, có lẽ là một jsFiddle? Có vẻ như bạn nên sử dụng indexthuộc tính này hơn là itemcho thuộc tính của bạnsplice
Xotic750

@ Xotic750 Xin lỗi, đã bổ sung làm rõ.
novicePrgrmr

Câu trả lời:


235

Có vẻ như bạn đang cố gắng làm điều này?

Lặp lại và thay đổi một mảng bằng 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>

Điều này hoạt động tốt cho trường hợp đơn giản khi bạn không có 2 trong số các giá trị giống nhau như các mục mảng liền kề, bạn có thể gặp vấn đề này.

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>

Vậy chúng ta có thể làm gì với vấn đề này khi lặp và thay đổi một mảng? Giải pháp thông thường là làm việc ngược lại. Sử dụng ES3 trong khi nhưng bạn có thể sử dụng cho đường nếu thích

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>

Được, nhưng bạn muốn sử dụng các phương pháp lặp lại ES5. Tốt và tùy chọn sẽ là sử dụng Array.prototype.filter nhưng điều này không làm thay đổi mảng ban đầu mà tạo ra một mảng mới, vì vậy trong khi bạn có thể nhận được câu trả lời chính xác, nó không phải là những gì bạn đã chỉ định.

Chúng ta cũng có thể sử dụng ES5 Array.prototype.reduceRight , không phải cho thuộc tính giảm của nó bằng thuộc tính lặp của nó, tức là lặp ngược lại.

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>

Hoặc chúng ta có thể sử dụng ES5 Array.protoype.indexOf như vậy.

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>

Nhưng bạn đặc biệt muốn sử dụng ES5 Array.prototype.forEach , vậy chúng ta có thể làm gì? Vâng, chúng ta cần sử dụng Array.prototype.slice để tạo một bản sao nông của mảng và Array.prototype.reverse để chúng ta có thể làm việc ngược lại để thay đổi mảng ban đầu.

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>

Cuối cùng, ES6 cung cấp cho chúng tôi một số lựa chọn thay thế khác, nơi chúng tôi không cần phải tạo các bản sao cạn và đảo ngược chúng. Đáng chú ý là chúng ta có thể sử dụng Trình tạo và Trình lặp lại . Tuy nhiên, mức hỗ trợ hiện tại là khá thấp.

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>

Một điều cần lưu ý trong tất cả những điều trên là, nếu bạn loại bỏ NaN khỏi mảng thì việc so sánh với bằng sẽ không hoạt động vì trong Javascript NaN === NaNlà sai. Nhưng chúng tôi sẽ bỏ qua điều đó trong các giải pháp vì nó là một trường hợp cạnh không xác định khác.

Vì vậy, chúng tôi có nó, một câu trả lời đầy đủ hơn với các giải pháp vẫn còn các trường hợp cạnh. Ví dụ mã đầu tiên vẫn đúng nhưng như đã nêu, nó không phải là không có vấn đề.


Cảm ơn vì đã trả lời. Tôi đã thử sử dụng giải pháp của bạn, nhưng nó vẫn không xóa phần tử khỏi mảng. Tôi sẽ đưa ra các chi tiết trong câu hỏi.
novicePrgrmr

Đặt console.log(review);sau forEach, như trong ví dụ của tôi.
Xotic750

4
Hãy cẩn thận, điều này sẽ bị hỏng nếu hai phần tử liên tiếp cần xóa: var review = ['a', 'a', 'c', 'b', 'a']; sẽ mang lại ['a', 'c', 'b']
quentinadam

3
LƯU Ý - Câu trả lời này là SAI! Foreach lặp qua mảng theo chỉ mục. Khi bạn xóa các phần tử trong khi lặp lại chỉ mục của các mục sau sẽ thay đổi. Trong ví dụ này, khi bạn xóa chữ 'a' đầu tiên, số chỉ mục 1 bây giờ sẽ trở thành 'c'. Do đó chữ 'b' đầu tiên thậm chí không được đánh giá. Vì bạn đã không cố gắng xóa nó nên mọi việc vẫn ổn, nhưng đó không phải là cách. Bạn nên lặp qua bản sao ngược của mảng, sau đó xóa các mục trong mảng ban đầu.
danbars

4
@ Xotic750 - câu trả lời ban đầu (hiện là đoạn mã đầu tiên) sai đơn giản vì forEach sẽ không lặp lại tất cả các phần tử trong mảng, như tôi đã giải thích trong nhận xét trước. Tôi biết rằng câu hỏi là làm thế nào để loại bỏ các phần tử trong vòng lặp forEach, nhưng câu trả lời đơn giản là bạn không làm điều đó. Vì nhiều người đang đọc những câu trả lời đó và nhiều lần sao chép câu trả lời một cách mù quáng (đặc biệt là những câu trả lời được chấp nhận), điều quan trọng là phải lưu ý những sai sót trong mã. Tôi nghĩ rằng điều ngược lại khi vòng lặp là giải pháp đơn giản nhất, hiệu quả nhất và dễ đọc nhất và nên là câu trả lời được chấp nhận
danbars

37

Sử dụng Array.prototype.filterthay vì 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

Mặc dù câu trả lời của Xotic750 cung cấp một số điểm tốt và các giải pháp khả thi, nhưng đôi khi đơn giản lại tốt hơn .

Bạn biết rằng mảng đang được lặp lại đang bị đột biến trong chính quá trình lặp (tức là loại bỏ một mục => thay đổi chỉ mục), do đó, logic đơn giản nhất là quay ngược lại theo kiểu cũ for( ngôn ngữ 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());

Nếu bạn thực sự nghĩ về nó, a forEachchỉ là một đường cú pháp cho một forvòng lặp ... Vì vậy, nếu nó không giúp ích cho bạn, chỉ cần dừng đầu của bạn chống lại nó.


1

Bạn cũng có thể sử dụng indexOf để thực hiện việc này

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

1

Tôi hiểu rằng bạn muốn xóa khỏi mảng bằng cách sử dụng một điều kiện và có một mảng khác có các mục bị xóa khỏi mảng. Đúng?

Còn cái này thì sao?

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

Hy vọng điều này giúp đỡ...

Nhân tiện, tôi đã so sánh 'for-loop' với 'forEach'.

Nếu loại bỏ trong trường hợp một chuỗi chứa 'f', kết quả sẽ khác.

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

Và loại bỏ bởi mỗi lần lặp lại, kết quả cũng khác nhau.

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

Sau đây sẽ cung cấp cho bạn tất cả các yếu tố không bằng các ký tự đặc biệt của bạn!

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

0

Đây là cách bạn nên làm điều đó:

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

1
Tôi không nghĩ là trường hợp này. Tôi đã thay đổi mã của mình giả sử p là một chỉ mục và bây giờ nó thậm chí không vào được ifcâu lệnh.
novicePrgrmr

3
@WhoCares Bạn sẽ thấy thông số ecma-international.org/ecma-262/5.1/#sec-15.4.4.18 Các đối số của hàm callBack làitem, index, object
Xotic750
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.