Một phép tính tổng chạy tốt khi danh sách có dữ liệu nhưng đột ngột ném TypeError sau khi bộ lọc trả về mảng rỗng. Nguyên nhân thường nằm ở lời gọi reduce không truyền initialValue. Tuy nhiên, thêm 0 chỉ giải quyết điểm bắt đầu của phép cộng; dữ liệu đầu vào vẫn cần được kiểm tra riêng.
1. Mảng rỗng cần một kết quả được định nghĩa rõ
try {
[].reduce((sum, value) => sum + value);
} catch (error) {
console.log(error instanceof TypeError); // true
}
console.log([].reduce((sum, value) => sum + value, 0)); // 0
console.log([2, 3].reduce((sum, value) => sum + value, 0)); // 5
Khi không có initialValue, reduce lấy phần tử có mặt đầu tiên làm accumulator và bắt đầu callback từ phần tử có mặt tiếp theo. Nếu không tìm được phần tử nào, phương thức ném lỗi. Khi truyền 0, tổng của danh sách rỗng được xác định là 0 và callback không cần chạy.
Giá trị khởi tạo phụ thuộc phép toán. Tích thường bắt đầu bằng 1; ghép danh sách có thể bắt đầu bằng mảng rỗng. Với phép chọn giá trị lớn nhất, cần xác định riêng ý nghĩa của đầu vào rỗng thay vì mặc định dùng 0, vì danh sách có thể toàn số âm.
2. Một phần tử có thể bỏ qua callback hoàn toàn
let calls = 0;
const result = ['bad'].reduce((sum, value) => {
calls += 1;
if (typeof value !== 'number') throw new TypeError('Number required');
return sum + value;
});
console.log(result); // bad
console.log(calls); // 0
Nếu không truyền initialValue, phần tử đầu tiên trở thành accumulator mà không đi qua logic kiểm tra value. Ví dụ chỉ có một phần tử nên callback không được gọi lần nào. Đặt việc kiểm tra trong callback nhưng quên giá trị khởi tạo có thể khiến dữ liệu sai vượt qua.
3. Thêm 0 không chặn được ép kiểu và ô trống
console.log(['2', 3].reduce((sum, value) => sum + value, 0)); // 023
const sparse = [2, , 3];
let calls = 0;
console.log(sparse.reduce((sum, value) => {
calls += 1;
return sum + value;
}, 0)); // 5
console.log(calls); // 2
Toán tử + có thể nối chuỗi: 0 cộng chuỗi '2' thành '02', rồi nối tiếp 3. Giá trị khởi tạo không phải bộ kiểm tra kiểu. Với mảng thưa thông thường, reduce bỏ qua ô không có phần tử; ô trống cũng không giống phần tử mang giá trị undefined.
4. Định nghĩa hợp đồng cho danh sách số lượng
Ví dụ sau nhận mảng thông thường chứa số lượng nguyên không âm trong phạm vi số nguyên an toàn. Array.from biến ô trống thành undefined để callback có thể từ chối; bước này tạo thêm một mảng, phù hợp ví dụ danh sách nhỏ. Với luồng dữ liệu lớn, có thể dùng vòng lặp kiểm tra và cộng trực tiếp để tránh bản sao.
function totalCounts(values) {
if (!Array.isArray(values)) throw new TypeError('Array required');
return Array.from(values).reduce((sum, value) => {
if (!Number.isSafeInteger(value) || value < 0) {
throw new TypeError('Nonnegative safe integer required');
}
const next = sum + value;
if (!Number.isSafeInteger(next)) throw new RangeError('Total too large');
return next;
}, 0);
}
console.log(totalCounts([])); // 0
console.log(totalCounts([2, 3])); // 5
try {
totalCounts([2, , 3]);
} catch (error) {
console.log(error instanceof TypeError); // true
}
Number.isSafeInteger không chuyển chuỗi thành số và cũng loại NaN, Infinity, số thập phân. Kiểm tra tổng giúp phát hiện kết quả vượt phạm vi an toàn ngay cả khi từng phần tử đều hợp lệ. Hợp đồng này dành cho số lượng nguyên; không áp dụng nguyên xi cho số đo thập phân.
5. Kiểm tra cả đầu vào và giá trị trả về
- Mảng rỗng và mảng một phần tử phải có kết quả đã định nghĩa.
- Chuỗi số, null, undefined, NaN, số âm và số thập phân phải theo đúng chính sách dữ liệu.
- Ô trống cần được chấp nhận hoặc từ chối có chủ đích.
- Tổng vượt Number.MAX_SAFE_INTEGER cần được phát hiện.
- Mỗi nhánh callback phải trả về accumulator tiếp theo.
Các ví dụ được chạy bằng Node.js, kèm kiểm tra dữ liệu sai và giới hạn tổng. Bài không đo hiệu năng trên trình duyệt. Tham khảo tài liệu Array.prototype.reduce của MDN.




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