Lập trình · 21/10/2026

JavaScript splice trong vòng lặp tiến: vì sao xóa sót phần tử?

Đã chạy hết vòng lặp nhưng vài phần tử cần xóa vẫn còn trong mảng. Khi splice làm các phần tử dịch sang trái, biến chỉ mục của vòng lặp tiến vẫn tăng lên. Kết quả là một phần tử có thể đi qua vị trí kiểm tra mà chưa được xét.

JavaScript splice trong vòng lặp tiến: vì sao xóa sót phần tử?

Đã chạy hết vòng lặp nhưng vài phần tử cần xóa vẫn còn trong mảng. Khi splice làm các phần tử dịch sang trái, biến chỉ mục của vòng lặp tiến vẫn tăng lên. Kết quả là một phần tử có thể đi qua vị trí kiểm tra mà chưa được xét.

1. Tái hiện bằng các số chẵn liền nhau

const values = [2, 4, 5, 6, 8];
const visited = [];
for (let index = 0; index < values.length; index += 1) {
  visited.push(values[index]);
  if (values[index] % 2 === 0) values.splice(index, 1);
}
console.log(JSON.stringify(values)); // [4,5,8]
console.log(visited.join('|')); // 2|5|6

Sau khi xóa 2 tại chỉ mục 0, số 4 chuyển về vị trí 0 nhưng lượt sau kiểm tra vị trí 1. Khi xóa 6, số 8 lại chuyển vào chỗ vừa xóa rồi bị bỏ qua. splice sửa trực tiếp mảng nguồn; xem MDN splice.

Đổi điều kiện thành index nhỏ hơn độ dài ban đầu không tự sửa được lỗi dịch chỉ mục. Đoạn visited giúp nhìn rõ phần tử nào thực sự được kiểm tra, thay vì chỉ quan sát số lần lặp hoặc độ dài cuối cùng.

2. Tạo danh sách mới khi chỉ cần giữ các mục phù hợp

const values = [2, 4, 5, 6, 8];
const remaining = values.filter(value => value % 2 !== 0);
console.log(JSON.stringify(remaining)); // [5]
console.log(JSON.stringify(values)); // [2,4,5,6,8]
console.log(remaining === values); // false

filter biểu diễn điều kiện giữ lại phần tử và tạo một mảng mới; tham khảo MDN filter. Callback trong ví dụ chỉ kiểm tra số, không xóa hoặc thêm vào mảng nguồn.

Ví dụ dùng mảng đặc chứa số đã hợp lệ. Với object, filter không sao chép sâu từng bản ghi; nếu sửa trường của object còn lại, nơi giữ tham chiếu tới object đó cũng có thể thấy thay đổi. Tạo danh sách mới và tạo bản sao sâu dữ liệu là hai quyết định riêng.

3. Duyệt ngược nếu contract cần sửa cùng mảng

function removeEvensInPlace(values) {
  for (let index = values.length - 1; index >= 0; index -= 1) {
    if (values[index] % 2 === 0) values.splice(index, 1);
  }
  return values;
}
const values = [2, 4, 5, 6, 8];
const shared = values;
const remaining = removeEvensInPlace(values);
console.log(JSON.stringify(values)); // [5]
console.log(remaining === shared); // true
console.log(shared.length); // 1

Helper minh họa nhận mảng đặc, có thể sửa và chứa số hợp lệ đã được kiểm tra trước. Khi xóa tại chỉ mục hiện tại, phần chưa xét nằm ở các chỉ mục nhỏ hơn. Vòng lặp đi lùi nên không nhảy qua mục chưa kiểm tra trong thuật toán này.

shared vẫn là chính mảng đó và thấy danh sách đã đổi. Nếu yêu cầu là bảo toàn nguồn, cách này không đáp ứng dù kết quả phần tử đúng. Ngược lại, chỉ gán một biến sang mảng filter mới không cập nhật những nơi khác đang giữ mảng cũ.

Duyệt ngược cũng đổi thứ tự kiểm tra từ cuối về đầu. Không thêm gọi API, ghi log nghiệp vụ hoặc callback có tác dụng phụ vào helper rồi mặc định thứ tự thực hiện giống vòng lặp tiến. Nếu cần thứ tự tác vụ cụ thể, thiết kế nó riêng với bước chọn dữ liệu.

4. Chọn theo quyền sở hữu dữ liệu

Nhu cầuCách tiếp cận trong bài
Giữ nguyên danh sách nguồnfilter với predicate không sửa nguồn
Sửa cùng mảng cho các tham chiếu dùng chungDuyệt ngược rồi splice theo chỉ mục
Cần giữ thứ tự tác dụng phụTách quyết định chọn khỏi thực thi công việc

Không dùng vài phần tử cách xa nhau làm toàn bộ bộ test. Các trường hợp cần có là hai mục cần xóa liền nhau, mục đầu và cuối, tất cả bị xóa, không mục nào bị xóa và mảng rỗng. Kiểm tra cả nội dung, thứ tự và tham chiếu đầu ra theo contract.

Sáu khối mã Việt–Anh đã chạy bằng Node.js, kèm kiểm tra các trường hợp biên của helper duyệt ngược. Bài không benchmark hai phương án và chưa tích hợp component giao diện.

Thảo luận

Bình luận 0

Đăng nhập để bình luận

Bạn cần có tài khoản để tham gia thảo luận và trả lời độc giả khác.

Đăng nhậpĐăng ký

Chưa có bình luận. Hãy là người đầu tiên chia sẻ ý kiến.