F8

FULLSTACK HUB

Bài Tập 30 tháng 6, 2026 lúc 19:51
Thuộc bài học:[Buổi 16] - Day 16: Array, String, Number trong JavaScript

[Bài Tập] Bộ ba nguyên tử: Map, Filter, Reduce

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à duBaoTraffic thỏ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 danhSachTrang có bị thay đổi dữ liệu không. Nếu danhSachTrang[0].traffic biến thành 6000 là bạn đã dính bẫy Tham chiếu và thất bại. Mảng gốc phải giữ nguyên 5000.

// 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à trangYeuKem lọc ra các trang thỏa mãn ĐỒNG THỜI cả 2 điều kiện:

    1. tinhTrang"can-toi-uu".

    2. traffic nhỏ hơn 1000.

  • 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-seo có traffic 1200 (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ốc danhSachTrang. (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 =

Bài Tập & Chấm Điểm
💻 Code của học viên
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" }
];


// Viết code Bài 1 của bạn ở đây...
// let duBaoTraffic = danhSachTrang.map(data => data.traffic * 1.2);
// console.log(duBaoTraffic);

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

console.log(duBaoTraffic);


// Viết code Bài 2 của bạn ở đây...
let trangYeuKem = danhSachTrang.filter(item => {
    if (item.traffic <= 1000 && item.tinhTrang === "can-toi-uu") return item
});

// console.log(trangYeuKem);
// 3A
let tongTraffic = danhSachTrang.reduce((total, currentTraffic) => {
    return total + currentTraffic.traffic
}, 0);

// console.log(tongTraffic);

// 3B
let thongKeTinhTrang = danhSachTrang.reduce((acc, curr) => {
    if (curr.tinhTrang === "tot") {
        return acc.tot + 1
    } else if (curr.tinhTrang === "can-toi-uu") {
        return acc["can-toi-uu"] + 1;
    }
}, {tot: 0, "can-toi-uu": 0});

// { tot: 2, "can-toi-uu": 2 }

// console.log(thongKeTinhTrang);


Giảng viên góp ý

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 filterreduce 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ính traffic là đượ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):

JavaScript
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ảng duBaoTraffic sẽ 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ốc danhSachTrang.

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ùng if (...) return item cho thấy bạn vẫn bị tư duy của vòng lặp for cũ ảnh hưởng.

  • Bản chất của filter là 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):

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

JavaScript
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.

JavaScript
// Đ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ánh if đầu tiên và thực hiện lệnh: return acc.tot + 1. Lúc này acc.tot đang là 0, cộng 1 bằng 1. Bạn ra lệnh return 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ố 1 sang vòng 2!

  • Vòng 2: Biến tích lũy acc lú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ạy return 1["can-toi-uu"] + 1. Trong JavaScript, con số 1 là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):

JavaScript
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é!".