Mở máy, tạo một file test.js và nạp mảng dữ liệu gốc này vào:
const danhSachTrang = [
{ id: 1, url: "/trang-chu", traffic: 5000, tinhTrang: "tot" },
{ id: 2, url: "/dich-vu-seo", traffic: 1200, tinhTrang: "can-toi-uu" },
{ id: 3, url: "/blog/bai-viet-1", traffic: 800, tinhTrang: "tot" },
{ id: 4, url: "/lien-he", traffic: 150, tinhTrang: "can-toi-uu" }
];Bài 1: map() — Chiến dịch tăng trưởng (Dự báo tương lai)
Sếp yêu cầu bạn làm một bản báo cáo dự kiến: "Nếu tháng sau tối ưu tốt, traffic của TẤT CẢ các trang sẽ tăng thêm 20% (tức là lấy traffic * 1.2)".
Yêu cầu: Hãy tạo ra mảng mới tên là
duBaoTrafficthỏa mãn điều kiện tăng trưởng trên.ĐIỀU KIỆN SỐNG CÒN: Hãy kiểm tra xem mảng gốc
danhSachTrangcó bị thay đổi dữ liệu không. NếudanhSachTrang[0].trafficbiến thành6000là bạn đã dính bẫy Tham chiếu và thất bại. Mảng gốc phải giữ nguyên5000.
// Viết code Bài 1 của bạn ở đây...
let duBaoTraffic =Bài 2: filter() — Lọc "bệnh binh" mang đi cấp cứu
Bây giờ, bạn cần tìm ra những trang web đang hoạt động kém để ưu tiên tối ưu trước cho khách hàng.
Yêu cầu: Dựa vào mảng gốc
danhSachTrang, hãy tạo ra một mảng mới tên làtrangYeuKemlọc ra các trang thỏa mãn ĐỒNG THỜI cả 2 điều kiện:Có
tinhTranglà"can-toi-uu".- Có
trafficnhỏ hơn1000. Kết quả mong muốn: Mảng mới này chỉ được phép chứa đúng 1 phần tử duy nhất (trang
/lien-he). Thằng/dich-vu-seocó traffic1200(lớn hơn 1000) nên phải bị loại ra.
// Viết code Bài 2 của bạn ở đây...
let trangYeuKem =Bài 3: reduce() — Máy ép doanh số tổng lực
Cuối tháng, bạn phải làm báo cáo tổng kết hiệu năng cho toàn bộ dự án để gửi cho khách hàng xem tổng lượt truy cập là bao nhiêu.
Yêu cầu 3A (Mức độ CƠ BẢN): Hãy dùng
reduce()để tính Tổng traffic của tất cả các trang trong mảng gốcdanhSachTrang. (Kết quả phải ra chính xác là7150).Yêu cầu 3B (Mức độ TRÙM CUỐI): Vẫn dùng
reduce(), hãy biến đổi mảng gốc thành một Object duy nhất để đếm số lượng trạng thái web.Giá trị khởi tạo bắt đầu:
{ tot: 0, "can-toi-uu": 0 }- Kết quả đầu ra mong muốn:
{ tot: 2, "can-toi-uu": 2 }
// Viết code Bài 3A ở đây...
let tongTraffic = // Viết code Bài 3B ở đây...
let thongKeTinhTrang =
Có cả tin vui lẫn tin buồn đây. Tin vui là bạn đã nuốt trọn vẹn được tư duy của filter và reduce cơ bản. Tin buồn là ở bài trùm cuối 3B, bạn đã dính ngay một cú lừa kinh điển khiến code bị "sập nguồn" ngay lập tức.
Cùng mang code lên bàn cân để chấm điểm và sửa gắt từng lỗi một nhé!
Bài 1: map() — 6/10 điểm (Chạy được nhưng chưa đúng ý Sếp)
Vấn đề: Code của bạn chạy không lỗi, nhưng kết quả trả về lại là một mảng số thuần túy:
[6000, 1440, 960, 180]. Sếp muốn một bản báo cáo dữ liệu trang web, tức là mảng mới vẫn phải giữ nguyên cấu trúc Object (tên url, id, tình trạng) và chỉ có thuộc tínhtrafficlà được nhân lên 1.2 thôi.Cách sửa chuẩn chỉnh (Kết hợp toán tử Spread để chống dính Tham chiếu):
let duBaoTraffic = danhSachTrang.map(item => {
return {
...item, // Copy lại toàn bộ thuộc tính cũ (id, url, tinhTrang) sang Object mới
traffic: item.traffic * 1.2 // Ghi đè riêng thuộc tính traffic
};
});
Giải thích câu hỏi sống còn: Nhờ việc dùng toán tử
...itemđể tạo ra một Object mới hoàn toàn ở mỗi vòng lặp, mảngduBaoTrafficsẽ là một vùng nhớ độc lập, không hề đụng chạm hay làm biến đổi dữ liệu của mảng gốcdanhSachTrang.
Bài 2: filter() — 8/10 điểm (Kết quả đúng nhưng viết chưa "văn minh")
Vấn đề: Code của bạn ra đúng kết quả trang
/lien-he. Tuy nhiên, cú pháp dùngif (...) return itemcho thấy bạn vẫn bị tư duy của vòng lặpforcũ ảnh hưởng.Bản chất của
filterlà nó chỉ cần bạn trả về một giá trị Đúng/Sai (True/False). Nếu biểu thức đúng, nó tự động bốc phần tử đó bỏ vào mảng mới. Bạn không cần phải trả về chính phần tử đó.Cách viết Clean Code (Chuẩn phong cách JS hiện đại):
let trangYeuKem = danhSachTrang.filter(item => {
return item.traffic <= 1000 && item.tinhTrang === "can-toi-uu";
});
// Hoặc viết siêu ngắn trên 1 dòng (Implicit return):
let trangYeuKemGọn = danhSachTrang.filter(item => item.traffic <= 1000 && item.tinhTrang === "can-toi-uu");
Bài 3A: reduce() tính tổng — 10/10 điểm (Xuất sắc)
let tongTraffic = danhSachTrang.reduce((total, currentTraffic) => {
return total + currentTraffic.traffic
}, 0);
Không có một điểm trừ nào! Khởi tạo bằng 0, cộng dồn qua từng vòng và trả về chính xác 7150. Bài này bạn làm rất cứng.
Bài 3B: reduce() trùm cuối — 2/10 điểm (Ăn ngay quả Bug "Nát gáo")
Nhìn kỹ lại đoạn code này của bạn, nếu bạn cho chạy trên trình duyệt hoặc Node.js, kết quả nhận được sẽ không phải là Object mong muốn mà là NaN hoặc lỗi trắng màn hình.
// ĐOẠN CODE LỖI
let thongKeTinhTrang = danhSachTrang.reduce((acc, curr) => {
if (curr.tinhTrang === "tot") {
return acc.tot + 1 // ❌ SAI Ở ĐÂY
} else if (curr.tinhTrang === "can-toi-uu") {
return acc["can-toi-uu"] + 1; // ❌ SAI Ở ĐÂY
}
}, {tot: 0, "can-toi-uu": 0});
Tại sao sai? Hãy nhớ lại quy tắc "Chuyền Heo Đất":
Vòng 1: Phần tử đầu tiên có
tinhTrang: "tot". Code chạy vào nhánhifđầu tiên và thực hiện lệnh:return acc.tot + 1. Lúc nàyacc.totđang là0, cộng 1 bằng1. Bạn ra lệnhreturn 1;Điều này có nghĩa là bạn đã vứt luôn con heo đất Object đi, và chuyền con số
1sang vòng 2!Vòng 2: Biến tích lũy
acclúc này không còn là Object{tot: 0, "can-toi-uu": 0}nữa, mà nó chính là con số1. Khi gặp phần tử tiếp theo có tình trạng"can-toi-uu", code sẽ chạyreturn 1["can-toi-uu"] + 1. Trong JavaScript, con số1làm gì có thuộc tính nào tên là"can-toi-uu", nên nó sẽ trả vềundefined + 1=NaN. Toàn bộ các vòng sau nát bét!
Cách sửa đúng (Phải cập nhật ruột heo đất rồi trả về NGUYÊN CON HEO ĐẤT):
let thongKeTinhTrang = danhSachTrang.reduce((acc, curr) => {
if (curr.tinhTrang === "tot") {
acc.tot += 1; // Chỉ tăng giá trị bên trong Object lên 1
} else if (curr.tinhTrang === "can-toi-uu") {
acc["can-toi-uu"] += 1; // Chỉ tăng giá trị bên trong Object lên 1
}
<span class="hljs-keyword">return</span> acc; <span class="hljs-comment">// 🔥 BẮT BUỘC: Ôm nguyên con heo đất này chuyền sang vòng kế tiếp!</span>
}, { tot: 0, "can-toi-uu": 0 });
console.log(thongKeTinhTrang); // Kết quả chuẩn đét: { tot: 2, 'can-toi-uu': 2 }
Vấp ngã ở bài 3B này là cực kỳ tốt, vì 10 người mới học F8 thì hết 9 người viết giống bạn. Sai một lần thấy ngay kết quả NaN là từ nay về sau cứ viết reduce là não tự động nhắc: "Này, nhớ return nguyên con heo đất (acc) ở cuối hàm đấy nhé!".