Đổi left.concat(right) thành [...left, ...right] không phải lúc nào cũng giữ nguyên kết quả. Với hai mảng thông thường, hai cách thường trông tương đương. Khác biệt xuất hiện khi API trả nhầm chuỗi, khi đầu vào là Set hoặc khi danh sách có ô trống.
1. Một chuỗi có thể thành nhiều phần tử
console.log(JSON.stringify(['A'].concat('BC'))); // ["A","BC"]
console.log(JSON.stringify(['A', ...'BC'])); // ["A","B","C"]
const extra = new Set(['B', 'C']);
const joined = ['A'].concat(extra);
console.log(joined[1] === extra); // true
console.log(JSON.stringify(['A', ...extra])); // ["A","B","C"]
concat không duyệt mọi iterable để lấy phần tử; một chuỗi hay Set thông thường được thêm như một giá trị. Spread trong array literal duyệt iterable. Xem MDN Array.concat và MDN spread syntax.
Nếu right đáng lẽ là mảng mã sản phẩm nhưng lại thành chuỗi 'BC', đổi cú pháp có thể biến một lỗi schema thành hai mã 'B' và 'C' trông hợp lệ. Vì vậy, kiểm tra kiểu đầu vào trước khi ghép quan trọng hơn việc chọn cách viết ngắn.
2. Ô trống không được giữ giống nhau
const sparse = new Array(1);
const viaConcat = [].concat(sparse);
const viaSpread = [...sparse];
console.log(viaConcat.length); // 1
console.log(0 in viaConcat); // false
console.log(0 in viaSpread); // true
console.log(viaSpread[0] === undefined); // true
Với mảng thông thường trong ví dụ, concat giữ ô trống, còn spread tạo phần tử undefined tường minh. JSON.stringify cho hai kết quả này cùng biểu diễn [null], nên chỉ so sánh chuỗi JSON có thể bỏ sót khác biệt cấu trúc. Khi test, kiểm tra cả sự tồn tại của chỉ mục.
Mảng thưa còn có thể làm callback của phương thức mảng chạy khác dự kiến. Nếu contract cấm ô trống, từ chối hoặc chuẩn hóa chúng có chủ đích ở ranh giới nhận dữ liệu; đừng để một lần đổi cú pháp âm thầm quyết định chính sách.
3. Mảng mới vẫn có thể chia sẻ object cũ
const row = { count: 1 };
const original = [row];
const copied = original.concat([]);
copied[0].count = 2;
console.log(original[0].count); // 2
console.log(copied === original); // false
console.log(copied[0] === original[0]); // true
Mảng ngoài được tạo mới nhưng phần tử object vẫn là cùng tham chiếu. Array spread cũng không tự sao chép sâu object. Nếu giao diện cần sửa bản nháp độc lập, xác định độ sâu cần tách và cách xử lý cấu trúc lồng nhau trước khi chọn phương án sao chép.
4. Giới hạn đầu vào nếu chỉ muốn ghép hai mảng
function mergeLists(left, right) {
if (!Array.isArray(left) || !Array.isArray(right)) {
throw new TypeError('both inputs must be arrays');
}
return [...left, ...right];
}
console.log(JSON.stringify(mergeLists(['A'], ['BC']))); // ["A","BC"]
console.log(JSON.stringify(mergeLists([], []))); // []
Helper dành cho mảng dữ liệu thông thường, chẳng hạn mảng nhận từ JSON.parse. Nó từ chối chuỗi, Set, null và object chỉ có thuộc tính length. Nó giữ phần tử trùng, không kiểm tra schema từng phần tử và dùng quy tắc spread cho ô trống.
Array.isArray không bảo đảm mảng không bị tùy biến iterator. Với object JavaScript tùy ý, Proxy hoặc subclass có hành vi đặc biệt, cần contract rộng hơn; helper này không phải bộ chuẩn hóa mọi iterable. concat cũng có cơ chế Symbol.isConcatSpreadable để tùy biến việc trải phần tử, nên không nên suy diễn từ ví dụ đơn giản sang mọi object.
5. Checklist trước khi đổi concat sang spread
- Hai đầu vào có thật sự là mảng theo contract không?
- Chuỗi và Set phải bị từ chối hay được chuyển đổi rõ ràng?
- Ô trống có được phép và có cần giữ nguyên cấu trúc?
- Có cần sao chép object hoặc xử lý phần tử trùng?
- Test đã kiểm tra kiểu và chỉ mục, thay vì chỉ đối chiếu JSON?
Tám khối mã Việt–Anh đã chạy bằng Node.js, kèm kiểm tra helper từ chối sai kiểu và giữ thứ tự, phần tử trùng, tham chiếu object. Chưa đo hiệu năng hoặc tích hợp API thực tế.




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