Skip to content

📚 NGÀY 1: ES6+ Essentials cho React

📍 Phase 1, Week 1, Day 1 of 169

I. KNOWLEDGE CHECK-IN

✅ Kiến thức từ trước:

  • JavaScript cơ bản (variables, functions, loops)
  • HTML/CSS fundamentals
  • Cách browser chạy JavaScript

🎯 Hôm nay sẽ học:

  1. let/const và block scope
  2. Arrow functions và this binding
  3. Template literals
  4. Destructuring (objects & arrays)
  5. Spread/Rest operators
  6. Array methods: map, filter, reduce

🔗 Ngày mai sẽ dùng để:

  • Promises & async/await
  • ES6 modules (import/export)
  • Advanced array operations

II. MENTAL MODELS (15 phút)

🧠 Big Picture: Tại sao ES6+ quan trọng cho React?

Old JavaScript (ES5)          Modern JavaScript (ES6+)
─────────────────────────    ────────────────────────────
var everywhere               let/const với block scope
function() {}                () => {} concise syntax
string + concatenation       `template ${literals}`
Manual object picking        { destructuring }
.apply(), .call()            Spread ... operators
Loops everywhere             .map(), .filter(), .reduce()


            React Components sử dụng 90% ES6+

🎯 Learning Path Today:

1. Variables (let/const)
   └─→ Understanding scope là nền tảng

2. Arrow Functions
   └─→ 90% functions trong React

3. Template Literals
   └─→ Dynamic JSX content

4. Destructuring
   └─→ Props handling trong React

5. Spread/Rest
   └─→ Immutable state updates

6. Array Methods
   └─→ Rendering lists trong React

III. CONTENT DEEP DIVE

📌 1. LET/CONST & BLOCK SCOPE (30 phút)

Mental Model:

var: Phạm vi theo function (cũ, nên tránh)
  ┌─────────────────┐
  │  function() {   │
  │    var x = 1    │ ← Truy cập được ở bất cứ đâu trong function
  │    if (true) {  │
  │      var x = 2  │ ← Cùng một biến!
  │    }            │
  │  }              │
  └─────────────────┘

let/const: Phạm vi theo block (hiện đại)
  ┌─────────────────┐
  │  function() {   │
  │    let x = 1    │ ← Phạm vi bên ngoài
  │    if (true) {  │
  │      let x = 2  │ ← Biến khác (phạm vi block)
  │    }            │
  │    console(x)   │ ← Vẫn là 1
  │  }              │
  └─────────────────┘

❌ Anti-patterns:

javascript
// ❌ SAI: Sử dụng var (phạm vi dễ gây nhầm lẫn)
function processUsers() {
  var user = 'John';
  if (true) {
    var user = 'Jane'; // Ghi đè user bên ngoài!
  }
  console.log(user); // 'Jane' - không mong muốn!
}

// ❌ SAI: Thay đổi giá trị const primitive
const age = 25;
age = 26; // TypeError: Assignment to constant variable

// ❌ SAI: Sử dụng let khi giá trị không thay đổi
let API_URL = 'https://api.example.com';
// Nên dùng const - biểu thị tính bất biến

// ❌ SAI: Gán lại khi muốn mutation
const config = { theme: 'dark' };
config = { theme: 'light' }; // Lỗi!
// Đúng ra: config.theme = 'light'

✅ Best Practices:

javascript
// ✅ ĐÚNG: Mặc định dùng const
const MAX_RETRIES = 3;
const API_URL = 'https://api.example.com';

// ✅ ĐÚNG: Dùng let chỉ khi cần gán lại
let currentIndex = 0;
for (let i = 0; i < items.length; i++) {
  currentIndex = i;
}

// ✅ ĐÚNG: Phạm vi block ngăn ngừa lỗi không mong muốn
function processData() {
  const result = [];

  if (data.length > 0) {
    const firstItem = data[0]; // Chỉ tồn tại trong block này
    result.push(firstItem);
  }

  // firstItem không truy cập được ở đây - tốt!
  return result;
}

// ✅ ĐÚNG: const với object (tham chiếu không đổi)
const user = { name: 'John', age: 25 };
user.age = 26; // OK - thay đổi object
user.email = 'john@example.com'; // OK - thêm thuộc tính
// user = {}; // TypeError: Assignment to constant variable

🎯 Decision Framework:

Nên dùng const hay let?

├─ Biến này có bị gán lại không?
│  │
│  ├─ CÓ → Dùng let
│  │       Ví dụ: bộ đếm vòng lặp, biến tích lũy
│  │
│  └─ KHÔNG → Dùng const
│          Ví dụ: API URLs, functions, objects

└─ Khi nghi ngờ → Bắt đầu với const
                    Chỉ đổi sang let khi cần

📌 2. ARROW FUNCTIONS (30 phút)

Mental Model:

Regular Function              Arrow Function
────────────────             ─────────────────
function add(a, b) {         const add = (a, b) => {
  return a + b;                return a + b;
}                            }

                             // Ngắn gọn hơn với implicit return:
                             const add = (a, b) => a + b;

Điểm khác biệt chính: binding của `this`
────────────────────────────────
Regular: `this` phụ thuộc vào CÁCH function được gọi
Arrow:   `this` được binding THEO LEXICAL (từ phạm vi bao quanh)

❌ Anti-patterns (Các anti-pattern):

javascript
// ❌ WRONG: Arrow function làm method (mất this)
const person = {
  name: 'John',
  greet: () => {
    console.log(`Hi, I'm ${this.name}`); // this.name is undefined! (không xác định)
  },
};

// ❌ WRONG: Dùng braces không cần thiết với single expression
const double = (x) => {
  return x * 2; // Remove braces and return (bỏ braces và return)
};

// ❌ WRONG: Dùng parentheses cho single parameter
const square = (x) => x * x; // Remove parentheses (bỏ parentheses)

// ❌ WRONG: Dùng arrow function khi cần arguments object
const sum = () => {
  return Array.from(arguments).reduce((a, b) => a + b); // Error! (lỗi)
};

// ❌ WRONG: Implicit return object nhưng thiếu parentheses
const makeUser = (name) => {
  name: name;
}; // Returns undefined! (trả về không xác định)

✅ Best Practices (Thực hành tốt nhất):

javascript
// ✅ RIGHT: Single parameter, không cần parentheses
const square = (x) => x * x;

// ✅ RIGHT: Multiple parameters, dùng parentheses
const add = (a, b) => a + b;

// ✅ RIGHT: Không có parameters, dùng empty parentheses
const getRandom = () => Math.random();

// ✅ RIGHT: Implicit return cho single expression
const isEven = (num) => num % 2 === 0;

// ✅ RIGHT: Explicit return cho nhiều statements
const processUser = (user) => {
  const name = user.name.toUpperCase();
  const age = user.age + 1;
  return { name, age };
};

// ✅ RIGHT: Return object với parentheses
const makeUser = (name) => ({ name: name, active: true });

// ✅ RIGHT: Dùng regular function cho methods
const person = {
  name: 'John',
  greet: function () {
    // hoặc greet() { ... }
    console.log(`Hi, I'm ${this.name}`);
  },
};

// ✅ RIGHT: Arrow functions cho callbacks (giữ nguyên this)
class Timer {
  constructor() {
    this.seconds = 0;
  }

  start() {
    setInterval(() => {
      this.seconds++; // Arrow function giữ nguyên `this`
      console.log(this.seconds);
    }, 1000);
  }
}

🎯 When to Use What (Khi nào dùng cái nào):

Use Arrow Functions:
✓ Callbacks (array methods, event handlers)
✓ Short utility functions
✓ Khi cần lexical `this`
✓ Function expressions

Use Regular Functions:
✓ Object methods
✓ Khi cần `arguments` object
✓ Constructor functions
✓ Khi muốn `this` động

📌 3. TEMPLATE LITERALS (20 phút)

Mental Model (Mô hình tư duy):

Old Way (Concatenation)       Modern Way (Template Literals)
───────────────────────      ─────────────────────────────────
'Hello ' + name + '!'        `Hello ${name}!`

'Line 1\n' +                 `Line 1
'Line 2\n' +                  Line 2
'Line 3'                      Line 3`

'Value: ' + (x * 2)          `Value: ${x * 2}`

❌ Anti-patterns (Các anti-pattern):

javascript
// ❌ WRONG: Dùng concatenation cho chuỗi đơn giản
const greeting = 'Hello ' + user.name + ', welcome back!';

// ❌ WRONG: Nối chuỗi nhiều dòng xấu, khó đọc
const html =
  '<div class="card">' +
  '<h2>' +
  title +
  '</h2>' +
  '<p>' +
  description +
  '</p>' +
  '</div>';

// ❌ WRONG: Không tận dụng sức mạnh expression
const price = 'Total: $' + (quantity * unitPrice).toFixed(2);

// ❌ WRONG: Nested concatenation (nối chuỗi lồng nhau)
const message =
  'User ' + user.name + ' (' + user.role + ') logged in at ' + timestamp;

✅ Best Practices (Thực hành tốt nhất):

javascript
// ✅ RIGHT: Clean interpolation (nội suy chuỗi rõ ràng, sạch sẽ)
const greeting = `Hello ${user.name}, welcome back!`;

// ✅ RIGHT: Multi-line strings (chuỗi nhiều dòng)
const html = `
  <div class="card">
    <h2>${title}</h2>
    <p>${description}</p>
  </div>
`;

// ✅ RIGHT: Expressions in templates (dùng biểu thức trong template)
const price = `Total: $${(quantity * unitPrice).toFixed(2)}`;

// ✅ RIGHT: Nested templates for readability (template lồng nhau, dễ đọc)
const message = `User ${user.name} (${user.role}) logged in at ${timestamp}`;

// ✅ RIGHT: Function calls in templates (gọi hàm trong template)
const status = `Status: ${isActive ? 'Active' : 'Inactive'}`;

// ✅ RIGHT: Complex expressions (biểu thức phức tạp)
const summary = `
  Order #${orderId}
  Items: ${items.length}
  Total: $${items.reduce((sum, item) => sum + item.price, 0)}
`;

// ✅ RIGHT: Tagged templates (advanced) (tagged template – nâng cao)
function highlight(strings, ...values) {
  return strings.reduce((result, str, i) => {
    return result + str + (values[i] ? `<mark>${values[i]}</mark>` : '');
  }, '');
}

const name = 'John';
const age = 25;
const bio = highlight`Name: ${name}, Age: ${age}`;
// "Name: <mark>John</mark>, Age: <mark>25</mark>"

📌 4. DESTRUCTURING (45 phút)

Mental Model - Object Destructuring (Mô hình tư duy – Object Destructuring):

Traditional Way              Destructuring Way
───────────────             ─────────────────
const name = user.name;     const { name, age, email } = user;
const age = user.age;
const email = user.email;

                            // Like unpacking a box:
                            // Box (object) → Individual items (variables)
                            // Giống như mở một cái hộp:
                            // Hộp (object) → Các món riêng lẻ (biến)

Mental Model - Array Destructuring (Mô hình tư duy – Array Destructuring):

Traditional Way              Destructuring Way
───────────────             ─────────────────
const first = arr[0];       const [first, second, third] = arr;
const second = arr[1];
const third = arr[2];

                            // Like taking items from a line:
                            // Position matters!
                            // Giống như lấy đồ theo hàng:
                            // Vị trí rất quan trọng!

❌ Anti-patterns (Các anti-pattern):

javascript
// ❌ WRONG: Tách object properties thủ công
function displayUser(user) {
  const name = user.name;
  const age = user.age;
  const email = user.email;
  console.log(name, age, email);
}

// ❌ WRONG: Truy cập nested properties nhiều lần
function getCity(user) {
  if (user.address && user.address.city) {
    return user.address.city.toUpperCase();
  }
}

// ❌ WRONG: Không dùng default values
function greet(user) {
  const name = user.name; // Could be undefined! (có thể không xác định)
  console.log(`Hello ${name}`); // "Hello undefined"
}

// ❌ WRONG: Bỏ qua array items mà không dùng placeholder
const colors = ['red', 'green', 'blue'];
const red = colors[0];
const blue = colors[2]; // Skip green manually (bỏ green thủ công)

✅ Best Practices - Objects (Thực hành tốt nhất – Objects):

javascript
// ✅ RIGHT: Basic object destructuring
const user = { name: 'John', age: 25, email: 'john@example.com' };
const { name, age, email } = user;

// ✅ RIGHT: Rename variables (đổi tên biến)
const { name: userName, age: userAge } = user;
console.log(userName); // 'John'

// ✅ RIGHT: Default values (giá trị mặc định)
const { name, role = 'user' } = user;
console.log(role); // 'user' (if not in object) (nếu không có trong object)

// ✅ RIGHT: Nested destructuring (destructuring lồng nhau)
const user = {
  name: 'John',
  address: {
    city: 'New York',
    zip: '10001',
  },
};
const {
  address: { city, zip },
} = user;

// ✅ RIGHT: Function parameters (tham số hàm)
function displayUser({ name, age, email = 'N/A' }) {
  console.log(`${name}, ${age}, ${email}`);
}
displayUser(user);

// ✅ RIGHT: Destructuring with rest (kết hợp rest)
const { name, ...otherProps } = user;
console.log(otherProps); // { age: 25, email: '...' }

// ✅ RIGHT: Computed property names (tên thuộc tính động)
const key = 'name';
const { [key]: value } = user;
console.log(value); // 'John'

✅ Best Practices - Arrays (Thực hành tốt nhất – Arrays):

javascript
// ✅ RIGHT: Basic array destructuring
const colors = ['red', 'green', 'blue'];
const [first, second, third] = colors;

// ✅ RIGHT: Skipping items (bỏ qua phần tử)
const [red, , blue] = colors; // Skip green (bỏ green)

// ✅ RIGHT: Default values (giá trị mặc định)
const [a, b, c = 'yellow'] = ['red', 'green'];
console.log(c); // 'yellow'

// ✅ RIGHT: Rest elements (phần tử rest)
const [first, ...rest] = colors;
console.log(rest); // ['green', 'blue']

// ✅ RIGHT: Swapping variables (hoán đổi biến)
let a = 1,
  b = 2;
[a, b] = [b, a]; // Swap without temp variable (hoán đổi không cần biến tạm)

// ✅ RIGHT: Function return values (giá trị trả về từ hàm)
function getCoordinates() {
  return [10, 20];
}
const [x, y] = getCoordinates();

// ✅ RIGHT: Nested array destructuring (destructuring mảng lồng nhau)
const matrix = [
  [1, 2],
  [3, 4],
];
const [[a, b], [c, d]] = matrix;

🎯 Real-world Example (Ví dụ thực tế):

javascript
// API response handling (xử lý phản hồi API)
const apiResponse = {
  data: {
    user: {
      id: 1,
      profile: {
        name: 'John Doe',
        email: 'john@example.com',
      },
      settings: {
        theme: 'dark',
        notifications: true,
      },
    },
  },
  status: 200,
};

// ✅ Extract exactly what you need (chỉ trích xuất đúng thứ cần dùng)
const {
  data: {
    user: {
      profile: { name, email },
      settings: { theme = 'light' },
    },
  },
  status,
} = apiResponse;

console.log(name, email, theme, status);
// 'John Doe', 'john@example.com', 'dark', 200

📌 5. SPREAD/REST OPERATORS (45 phút)

Mental Model (Mô hình tư duy):

Spread (...)                  Rest (...)
────────────                 ──────────
"Explode" array/object       "Collect" into array
(làm bung mảng/object)       (gom lại thành mảng)

const arr = [1, 2, 3];       function sum(...numbers) {
console.log(...arr);           // numbers is [1, 2, 3] (numbers là [1, 2, 3])
// Same as: 1, 2, 3            return numbers.reduce(...)
                             }
[...arr1, ...arr2]           sum(1, 2, 3)

{...obj1, ...obj2}           const [first, ...rest] = arr

❌ Anti-patterns (Các anti-pattern):

javascript
// ❌ WRONG: Manual array copying (mutates original)
// copy mảng thủ công (làm thay đổi mảng gốc)
const original = [1, 2, 3];
const copy = original; // Same reference! (cùng tham chiếu)
copy.push(4); // Mutates original too (làm thay đổi cả mảng gốc)

// ❌ WRONG: Verbose array concatenation
// nối mảng dài dòng
const combined = arr1.concat(arr2).concat(arr3);

// ❌ WRONG: Object.assign when spread is cleaner
// dùng Object.assign khi spread gọn hơn
const merged = Object.assign({}, obj1, obj2, obj3);

// ❌ WRONG: Manually building new object
// tạo object mới thủ công, dài dòng
const updated = {
  name: user.name,
  age: user.age,
  email: user.email,
  verified: true,
};

// ❌ WRONG: Using arguments object
// dùng arguments object
function sum() {
  let total = 0;
  for (let i = 0; i < arguments.length; i++) {
    total += arguments[i];
  }
  return total;
}

✅ Best Practices - Spread (Thực hành tốt nhất – Spread):

javascript
// ✅ RIGHT: Array copying (sao chép mảng)
const original = [1, 2, 3];
const copy = [...original]; // New array (mảng mới)
copy.push(4); // Original unchanged (mảng gốc không đổi)

// ✅ RIGHT: Array concatenation (nối mảng)
const combined = [...arr1, ...arr2, ...arr3];

// ✅ RIGHT: Add items to array (thêm phần tử vào mảng)
const numbers = [2, 3, 4];
const moreNumbers = [1, ...numbers, 5]; // [1, 2, 3, 4, 5]

// ✅ RIGHT: Object copying and merging (sao chép và gộp object)
const user = { name: 'John', age: 25 };
const updatedUser = { ...user, age: 26 };
// { name: 'John', age: 26 }

// ✅ RIGHT: Merge multiple objects (right to left priority)
// gộp nhiều object (ưu tiên từ phải sang trái)
const defaults = { theme: 'light', lang: 'en' };
const userPrefs = { theme: 'dark' };
const settings = { ...defaults, ...userPrefs };
// { theme: 'dark', lang: 'en' }

// ✅ RIGHT: Function arguments (truyền tham số hàm)
const numbers = [1, 2, 3, 4, 5];
Math.max(...numbers); // 5 (thay cho Math.max(1,2,3,4,5))

// ✅ RIGHT: Conditional properties (thuộc tính có điều kiện)
const user = {
  name: 'John',
  ...(isAdmin && { role: 'admin' }),
  ...(hasEmail && { email: 'john@example.com' }),
};

✅ Best Practices - Rest (Thực hành tốt nhất – Rest):

javascript
// ✅ RIGHT: Function with variable arguments (hàm với số lượng tham số linh hoạt)
function sum(...numbers) {
  return numbers.reduce((total, num) => total + num, 0);
}
sum(1, 2, 3, 4); // 10

// ✅ RIGHT: Separate first argument from rest (tách tham số đầu và phần còn lại)
function log(level, ...messages) {
  console.log(`[${level}]`, ...messages);
}
log('ERROR', 'Something', 'went', 'wrong');

// ✅ RIGHT: Destructuring with rest (destructuring kết hợp rest)
const { name, age, ...otherProps } = user;
// name='John', age=25, otherProps={email:'...', role:'...'}

// ✅ RIGHT: Array destructuring with rest (destructuring mảng với rest)
const [first, second, ...remaining] = [1, 2, 3, 4, 5];
// first=1, second=2, remaining=[3,4,5]

// ✅ RIGHT: Ignore some properties (bỏ qua một số thuộc tính)
const { password, ...safeUser } = user;
// Remove password, keep everything else (loại bỏ password, giữ phần còn lại)

🎯 React-Relevant Pattern:

javascript
// Immutable state updates (CRITICAL for React)

// ✅ Add item to array
const addItem = (items, newItem) => [...items, newItem];

// ✅ Remove item from array
const removeItem = (items, index) => [
  ...items.slice(0, index),
  ...items.slice(index + 1),
];

// ✅ Update item in array
const updateItem = (items, index, updates) => [
  ...items.slice(0, index),
  { ...items[index], ...updates },
  ...items.slice(index + 1),
];

// ✅ Update nested object
const user = {
  name: 'John',
  address: {
    city: 'New York',
    zip: '10001',
  },
};

const updatedUser = {
  ...user,
  address: {
    ...user.address,
    city: 'Boston',
  },
};

📌 6. ARRAY METHODS (60 phút)

Mental Model (Mô hình tư duy):

Traditional Loop              Modern Array Methods
────────────────             ─────────────────────
for (let i...) {             .map()    - Transform each item (biến đổi từng phần tử)
  // transform                .filter() - Keep items that pass test (giữ phần tử thỏa điều kiện)
}                            .reduce() - Combine into single value (gộp thành một giá trị)

                             .find()   - First item that matches (phần tử khớp đầu tiên)
                             .some()   - Any item matches? (có phần tử nào khớp không?)
                             .every()  - All items match? (tất cả phần tử có khớp không?)

🎯 Method Comparison (So sánh các method):

MethodReturns (Giá trị trả về)Mảng gốcUse Case (Trường hợp dùng)
mapNew array (same length)Không thay đổiTransform each item (biến đổi từng phần tử)
filterNew array (≤ length)Không thay đổiKeep matching items (giữ phần tử phù hợp)
reduceSingle valueKhông thay đổiCombine/aggregate (gộp / tổng hợp)
findSingle item or undefinedKhông thay đổiFirst match (khớp đầu tiên)
findIndexIndex or -1Không thay đổiPosition of match (vị trí phần tử khớp)
someBooleanKhông thay đổiAny match? (có phần tử nào khớp?)
everyBooleanKhông thay đổiAll match? (tất cả đều khớp?)
forEachundefinedKhông thay đổiSide effects only (chỉ dùng cho side effects)

Không thay đổi = tạo mảng mới (tham chiếu mới. Mức độ shallow copy)

❌ Anti-patterns (Các anti-pattern):

javascript
// ❌ WRONG: Dùng forEach khi map phù hợp hơn
const doubled = [];
numbers.forEach((num) => {
  doubled.push(num * 2);
});
// Should use map! (nên dùng map)

// ❌ WRONG: Dùng map cho side effects
users.map((user) => {
  console.log(user.name); // Side effect, not transformation (side effect, không phải biến đổi)
});
// Should use forEach! (nên dùng forEach)

// ❌ WRONG: Chaining map nhiều lần
const result = numbers
  .map((x) => x * 2)
  .map((x) => x + 1)
  .map((x) => x.toString());
// Should combine into single map! (nên gộp vào một map)

// ❌ WRONG: Dùng reduce khi có method đơn giản hơn
const hasAdmin = users.reduce((found, user) => {
  return found || user.role === 'admin';
}, false);
// Should use .some()! (nên dùng .some())

// ❌ WRONG: Mutating trong map
const updated = users.map((user) => {
  user.lastSeen = Date.now(); // MUTATION! (thay đổi trực tiếp)
  return user;
});

✅ MAP - Transform Each Item (MAP – Biến đổi từng phần tử):

javascript
// ✅ Basic transformation (biến đổi cơ bản)
const numbers = [1, 2, 3, 4, 5];
const doubled = numbers.map((num) => num * 2);
// [2, 4, 6, 8, 10]

// ✅ Extract properties (trích xuất thuộc tính)
const users = [
  { name: 'John', age: 25 },
  { name: 'Jane', age: 30 },
];
const names = users.map((user) => user.name);
// ['John', 'Jane']

// ✅ Transform objects (biến đổi object)
const usersWithIds = users.map((user, index) => ({
  id: index + 1,
  ...user,
}));
// [{ id: 1, name: 'John', age: 25 }, ...]

// ✅ Format for display (định dạng để hiển thị)
const formatted = users.map((user) => `${user.name} (${user.age} years old)`);

// ✅ Complex transformation (biến đổi phức tạp)
const products = [
  { name: 'Laptop', price: 1000, tax: 0.1 },
  { name: 'Mouse', price: 50, tax: 0.1 },
];
const withTotal = products.map((product) => ({
  ...product,
  total: product.price * (1 + product.tax),
}));

✅ FILTER - Keep Items That Match (FILTER – Giữ các phần tử thỏa điều kiện):

📌 Cú pháp

js
array.filter(callbackFn);
array.filter(callbackFn, thisArg);

📌 Tham số

callbackFn

Hàm được gọi một lần cho mỗi phần tử trong mảng.

  • Trả về truthy → phần tử được giữ lại
  • Trả về falsy → phần tử bị loại bỏ

callbackFn nhận 3 tham số:

Tham sốMô tả
elementPhần tử hiện tại đang được xử lý
indexChỉ số của phần tử hiện tại
arrayMảng gốc đang gọi filter()

thisArg (không bắt buộc)

Giá trị được dùng làm this khi thực thi callbackFn.


Các dạng thường gặp

javascript
// ✅ Basic filtering (lọc cơ bản)
const numbers = [1, 2, 3, 4, 5, 6];
const evens = numbers.filter((num) => num % 2 === 0);
// [2, 4, 6]

// ✅ Filter by property (lọc theo thuộc tính)
const users = [
  { name: 'John', age: 25, active: true },
  { name: 'Jane', age: 30, active: false },
  { name: 'Bob', age: 35, active: true },
];
const activeUsers = users.filter((user) => user.active);

// ✅ Multiple conditions (nhiều điều kiện)
const youngActiveUsers = users.filter((user) => user.active && user.age < 30);

// ✅ Remove falsy values (loại bỏ giá trị falsy)
const items = [1, 0, 'hello', '', null, 'world', undefined];
const truthy = items.filter(Boolean);
// [1, 'hello', 'world']

// ✅ Remove duplicates (with index check) (loại bỏ trùng lặp – dùng index)
const numbers = [1, 2, 2, 3, 3, 4];
const unique = numbers.filter((num, index, arr) => arr.indexOf(num) === index);
// [1, 2, 3, 4]

// ✅ Complex filtering (lọc phức tạp)
const products = [
  { name: 'Laptop', price: 1000, inStock: true },
  { name: 'Mouse', price: 50, inStock: false },
  { name: 'Keyboard', price: 100, inStock: true },
];
const affordable = products.filter((p) => p.price < 500 && p.inStock);

✅ REDUCE - Combine Into Single Value (REDUCE – Gộp thành một giá trị duy nhất):

javascript
// ✅ Sum numbers (tính tổng các số)
const numbers = [1, 2, 3, 4, 5];
const sum = numbers.reduce((total, num) => total + num, 0);
// 15

// ✅ Find maximum (tìm giá trị lớn nhất)
const max = numbers.reduce((max, num) => (num > max ? num : max), numbers[0]);

// ✅ Count occurrences (đếm số lần xuất hiện)
const fruits = ['apple', 'banana', 'apple', 'orange', 'banana'];
const count = fruits.reduce((acc, fruit) => {
  acc[fruit] = (acc[fruit] || 0) + 1;
  return acc;
}, {});
// { apple: 2, banana: 2, orange: 1 }

// ✅ Group by property (nhóm theo thuộc tính)
const users = [
  { name: 'John', role: 'admin' },
  { name: 'Jane', role: 'user' },
  { name: 'Bob', role: 'admin' },
];
const byRole = users.reduce((groups, user) => {
  const role = user.role;
  groups[role] = groups[role] || [];
  groups[role].push(user);
  return groups;
}, {});
// { admin: [{...}, {...}], user: [{...}] }

// ✅ Transform array to object (chuyển mảng thành object)
const users = [
  { id: 1, name: 'John' },
  { id: 2, name: 'Jane' },
];
const usersById = users.reduce((acc, user) => {
  acc[user.id] = user;
  return acc;
}, {});
// { 1: {...}, 2: {...} }

// ✅ Flatten nested arrays (làm phẳng mảng lồng nhau)
const nested = [[1, 2], [3, 4], [5]];
const flat = nested.reduce((acc, arr) => acc.concat(arr), []);
// [1, 2, 3, 4, 5]

// ✅ Calculate total price (tính tổng giá)
const cart = [
  { name: 'Laptop', price: 1000, quantity: 1 },
  { name: 'Mouse', price: 50, quantity: 2 },
];
const total = cart.reduce((sum, item) => sum + item.price * item.quantity, 0);
// 1100

✅ OTHER USEFUL METHODS (Các method hữu ích khác):

javascript
// ✅ FIND - First matching item (phần tử khớp đầu tiên)
const users = [
  { id: 1, name: 'John' },
  { id: 2, name: 'Jane' },
];
const user = users.find((u) => u.id === 2);
// { id: 2, name: 'Jane' }

// ✅ SOME - Any item matches? (có phần tử nào khớp không?)
const numbers = [1, 2, 3, 4, 5];
const hasEven = numbers.some((num) => num % 2 === 0);
// true

// ✅ EVERY - All items match? (tất cả đều khớp?)
const allEven = numbers.every((num) => num % 2 === 0);
// false

// ✅ INCLUDES - Array contains value? (mảng có chứa giá trị không?)
const hasThree = numbers.includes(3);
// true

// ✅ Chaining methods (kết hợp nhiều method)
const result = users
  .filter((user) => user.active)
  .map((user) => user.name)
  .sort();

🎯 React-Relevant Patterns (Các pattern liên quan tới React):

javascript
// ✅ Render list of components (map) (render danh sách component)
const UserList = ({ users }) => (
  <div>
    {users.map((user) => (
      <UserCard
        key={user.id}
        user={user}
      />
    ))}
  </div>
);

// ✅ Filter then render (lọc rồi render)
const ActiveUsers = ({ users }) => (
  <div>
    {users
      .filter((user) => user.active)
      .map((user) => (
        <UserCard
          key={user.id}
          user={user}
        />
      ))}
  </div>
);

// ✅ Calculate derived state (tính state dẫn xuất)
const total = items.reduce((sum, item) => sum + item.price, 0);
const average = total / items.length;

// ✅ Transform API data (biến đổi dữ liệu từ API)
const apiUsers = await fetch('/api/users').then((r) => r.json());
const formattedUsers = apiUsers.map((user) => ({
  id: user.user_id,
  name: `${user.first_name} ${user.last_name}`,
  email: user.email_address,
}));

IV. LIVE CODING DEMOS (45 phút)

🎯 Demo 1: User Profile Card (⭐)

javascript
// Requirement: Transform user data and display formatted info

const rawUser = {
  first_name: 'John',
  last_name: 'Doe',
  email: 'john.doe@example.com',
  age: 25,
  address: {
    street: '123 Main St',
    city: 'New York',
    state: 'NY',
    zip: '10001',
  },
  preferences: {
    theme: 'dark',
    notifications: true,
    language: 'en',
  },
};

// ❌ Old way
function formatUserOld(user) {
  const firstName = user.first_name;
  const lastName = user.last_name;
  const email = user.email;
  const city = user.address.city;
  const state = user.address.state;
  const theme = user.preferences.theme;

  return (
    'Name: ' +
    firstName +
    ' ' +
    lastName +
    '\n' +
    'Email: ' +
    email +
    '\n' +
    'Location: ' +
    city +
    ', ' +
    state +
    '\n' +
    'Theme: ' +
    theme
  );
}

// ✅ Modern way
const formatUser = (user) => {
  const {
    first_name: firstName,
    last_name: lastName,
    email,
    address: { city, state },
    preferences: { theme = 'light' },
  } = user;

  return `Name: ${firstName} ${lastName}
Email: ${email}
Location: ${city}, ${state}
Theme: ${theme}`;
};

console.log(formatUser(rawUser));

// ✅ Even better: Return object for flexibility (trả về object để linh hoạt hơn)

const transformUser = ({
  first_name,
  last_name,
  email,
  age,
  address: { city, state, ...restAddress },
  preferences,
}) => ({
  fullName: `${first_name} ${last_name}`,
  email,
  age,
  location: `${city}, ${state}`,
  address: restAddress,
  ...preferences,
});

const user = transformUser(rawUser);
console.log(user);
// {
//   fullName: 'John Doe',
//   email: 'john.doe@example.com',
//   age: 25,
//   location: 'New York, NY',
//   address: { street: '123 Main St', zip: '10001' },
//   theme: 'dark',
//   notifications: true,
//   language: 'en'
// }

🎯 Demo 2: Shopping Cart Manager (⭐⭐)

javascript
// Requirement: Manage shopping cart with modern JS

const products = [
  { id: 1, name: 'Laptop', price: 1000, category: 'electronics' },
  { id: 2, name: 'Mouse', price: 50, category: 'electronics' },
  { id: 3, name: 'Keyboard', price: 100, category: 'electronics' },
  { id: 4, name: 'Desk', price: 300, category: 'furniture' },
  { id: 5, name: 'Chair', price: 200, category: 'furniture' },
];

const cart = [
  { productId: 1, quantity: 1 },
  { productId: 2, quantity: 2 },
  { productId: 5, quantity: 1 },
];

// ✅ Calculate cart total
const calculateTotal = (cart, products) => {
  return cart.reduce((total, item) => {
    const product = products.find((p) => p.id === item.productId);
    return total + product.price * item.quantity;
  }, 0);
};

console.log(`Total: $${calculateTotal(cart, products)}`);
// Total: $1300

// ✅ Get cart items with full details
const getCartDetails = (cart, products) => {
  return cart.map((item) => {
    const product = products.find((p) => p.id === item.productId);
    return {
      ...product,
      quantity: item.quantity,
      subtotal: product.price * item.quantity,
    };
  });
};

const cartDetails = getCartDetails(cart, products);
console.log(cartDetails);

// ✅ Group cart by category
const groupByCategory = (cartDetails) => {
  return cartDetails.reduce((groups, item) => {
    const category = item.category;
    if (!groups[category]) {
      groups[category] = [];
    }
    groups[category].push(item);
    return groups;
  }, {});
};

const grouped = groupByCategory(cartDetails);
console.log(grouped);
// {
//   electronics: [{...}, {...}],
//   furniture: [{...}]
// }

// ✅ Add item to cart (immutably)
const addToCart = (cart, productId, quantity = 1) => {
  const existingIndex = cart.findIndex((item) => item.productId === productId);

  if (existingIndex !== -1) {
    // Update existing item
    return cart.map((item, index) =>
      index === existingIndex
        ? { ...item, quantity: item.quantity + quantity }
        : item
    );
  }

  // Add new item
  return [...cart, { productId, quantity }];
};

const newCart = addToCart(cart, 3, 1); // Add Keyboard
console.log(newCart);

// ✅ Remove item from cart (immutably)
const removeFromCart = (cart, productId) => {
  return cart.filter((item) => item.productId !== productId);
};

// ✅ Update quantity (immutably)
const updateQuantity = (cart, productId, quantity) => {
  if (quantity <= 0) {
    return removeFromCart(cart, productId);
  }

  return cart.map((item) =>
    item.productId === productId ? { ...item, quantity } : item
  );
};

// ✅ Apply discount
const applyDiscount = (total, discountPercent) => {
  return total * (1 - discountPercent / 100);
};

const finalTotal = applyDiscount(calculateTotal(cart, products), 10);
console.log(`Final Total (10% off): $${finalTotal}`);
// Final Total (10% off): $1170

🎯 Demo 3: Data Processing Pipeline (⭐⭐⭐)

javascript
// Requirement: Process and analyze user activity data

const activities = [
  {
    userId: 1,
    action: 'login',
    timestamp: '2025-01-01T10:00:00',
    duration: 0,
  },
  {
    userId: 1,
    action: 'view',
    timestamp: '2025-01-01T10:05:00',
    duration: 300,
  },
  {
    userId: 2,
    action: 'login',
    timestamp: '2025-01-01T10:10:00',
    duration: 0,
  },
  {
    userId: 1,
    action: 'purchase',
    timestamp: '2025-01-01T10:15:00',
    duration: 120,
    amount: 100,
  },
  {
    userId: 2,
    action: 'view',
    timestamp: '2025-01-01T10:20:00',
    duration: 180,
  },
  {
    userId: 3,
    action: 'login',
    timestamp: '2025-01-01T10:25:00',
    duration: 0,
  },
  {
    userId: 2,
    action: 'purchase',
    timestamp: '2025-01-01T10:30:00',
    duration: 90,
    amount: 50,
  },
  {
    userId: 1,
    action: 'logout',
    timestamp: '2025-01-01T10:35:00',
    duration: 0,
  },
];

// ✅ Group activities by user
const byUser = activities.reduce((groups, activity) => {
  const { userId, ...rest } = activity;
  groups[userId] = groups[userId] || [];
  groups[userId].push(rest);
  return groups;
}, {});

console.log(byUser);

// ✅ Calculate user statistics
const getUserStats = (activities) => {
  // Group by user first
  const byUser = activities.reduce((acc, activity) => {
    const { userId } = activity;
    acc[userId] = acc[userId] || [];
    acc[userId].push(activity);
    return acc;
  }, {});

  // Calculate stats for each user
  return Object.entries(byUser).map(([userId, userActivities]) => {
    const purchases = userActivities.filter((a) => a.action === 'purchase');
    const totalSpent = purchases.reduce((sum, p) => sum + (p.amount || 0), 0);
    const totalTime = userActivities.reduce((sum, a) => sum + a.duration, 0);

    return {
      userId: parseInt(userId),
      totalActivities: userActivities.length,
      totalPurchases: purchases.length,
      totalSpent,
      totalTime,
      averageSessionTime: totalTime / userActivities.length,
    };
  });
};

const stats = getUserStats(activities);
console.log(stats);
// [
//   { userId: 1, totalActivities: 4, totalPurchases: 1, totalSpent: 100, ... },
//   { userId: 2, totalActivities: 3, totalPurchases: 1, totalSpent: 50, ... },
//   { userId: 3, totalActivities: 1, totalPurchases: 0, totalSpent: 0, ... }
// ]

// ✅ Find top spenders
const getTopSpenders = (stats, limit = 3) => {
  return stats
    .filter((user) => user.totalSpent > 0)
    .sort((a, b) => b.totalSpent - a.totalSpent)
    .slice(0, limit)
    .map(({ userId, totalSpent, totalPurchases }) => ({
      userId,
      totalSpent,
      totalPurchases,
      averageOrderValue: totalSpent / totalPurchases,
    }));
};

const topSpenders = getTopSpenders(stats);
console.log('Top Spenders:', topSpenders);

// ✅ Activity timeline
const getTimeline = (activities) => {
  return activities
    .map(({ timestamp, action, userId, amount }) => ({
      time: new Date(timestamp).toLocaleTimeString(),
      event: `User ${userId}: ${action}${amount ? ` ($${amount})` : ''}`,
    }))
    .sort((a, b) => a.time.localeCompare(b.time));
};

const timeline = getTimeline(activities);
console.log('Timeline:');
timeline.forEach(({ time, event }) => {
  console.log(`${time} - ${event}`);
});

// ✅ Purchase analysis
const analyzePurchases = (activities) => {
  const purchases = activities.filter((a) => a.action === 'purchase');

  if (purchases.length === 0) {
    return { count: 0, total: 0, average: 0, max: 0, min: 0 };
  }

  const amounts = purchases.map((p) => p.amount);
  const total = amounts.reduce((sum, amount) => sum + amount, 0);

  return {
    count: purchases.length,
    total,
    average: total / purchases.length,
    max: Math.max(...amounts),
    min: Math.min(...amounts),
  };
};

const purchaseAnalysis = analyzePurchases(activities);
console.log('Purchase Analysis:', purchaseAnalysis);
// {
//   count: 2,
//   total: 150,
//   average: 75,
//   max: 100,
//   min: 50
// }

V. EXERCISES (90 phút)

💪 Exercise 1: Variable Practice (⭐) - 15 phút

Requirement: Refactor code sử dụng let/const đúng cách.

javascript
// Starter Code (BAD - using var):
var name = 'John Doe';
var age = 25;
var email = 'john@example.com';

if (age >= 18) {
  var status = 'adult';
  var canVote = true;
}

console.log(name, age, email, status, canVote);

for (var i = 0; i < 3; i++) {
  var message = 'Iteration ' + i;
  console.log(message);
}

console.log(i); // What value? Why?

Your Task:

  1. Replace var với let hoặc const appropriately
  2. Fix scope issues
  3. Add comments giải thích choices

Expected Output: Code chạy giống nhau nhưng safer.

💡 Solution
javascript
// ✅ Solution (Giải pháp):
const name = 'John Doe'; // Never changes (không bao giờ thay đổi)
const age = 25; // Never changes (không bao giờ thay đổi)
const email = 'john@example.com'; // Never changes (không bao giờ thay đổi)

let status; // Declared outside to be accessible (khai báo bên ngoài để có thể truy cập)
let canVote; // Declared outside to be accessible (khai báo bên ngoài để có thể truy cập)

if (age >= 18) {
  status = 'adult'; // Assigned in block (gán giá trị trong block)
  canVote = true; // Assigned in block (gán giá trị trong block)
}

console.log(name, age, email, status, canVote);

for (let i = 0; i < 3; i++) {
  // let i is block-scoped to loop (let i có phạm vi trong block của vòng lặp)
  const message = `Iteration ${i}`; // const in each iteration (const cho mỗi vòng lặp)
  console.log(message);
}

// console.log(i); // ReferenceError - i is not defined (good!) (lỗi tham chiếu – i không được định nghĩa, tốt!)

Key Points:

  • const for values that never change (dùng const cho giá trị không bao giờ thay đổi)
  • let for values that will be reassigned (dùng let cho giá trị sẽ được gán lại)
  • Block scope prevents accidental variable leaks (phạm vi block giúp ngăn rò rỉ biến ngoài ý muốn)

💪 Exercise 2: Arrow Functions & Destructuring (⭐⭐) - 25 phút

Requirement: Modernize function syntax và data extraction.

javascript
// Starter Code (OLD STYLE):
const users = [
  {
    id: 1,
    name: 'John Doe',
    email: 'john@example.com',
    address: { city: 'New York', country: 'USA' },
    age: 25,
  },
  {
    id: 2,
    name: 'Jane Smith',
    email: 'jane@example.com',
    address: { city: 'London', country: 'UK' },
    age: 30,
  },
  {
    id: 3,
    name: 'Bob Johnson',
    email: 'bob@example.com',
    address: { city: 'Paris', country: 'France' },
    age: 28,
  },
];

// Old way
function getUserInfo(user) {
  const name = user.name;
  const email = user.email;
  const city = user.address.city;
  const country = user.address.country;

  return (
    'Name: ' +
    name +
    '\n' +
    'Email: ' +
    email +
    '\n' +
    'Location: ' +
    city +
    ', ' +
    country
  );
}

function filterByAge(users, minAge) {
  const result = [];
  for (let i = 0; i < users.length; i++) {
    if (users[i].age >= minAge) {
      result.push(users[i]);
    }
  }
  return result;
}

function getEmails(users) {
  const emails = [];
  for (let i = 0; i < users.length; i++) {
    emails.push(users[i].email);
  }
  return emails;
}

Your Task:

  1. Convert all functions to arrow functions (chuyển tất cả functions sang arrow functions)
  2. Use destructuring trong parameters (dùng destructuring trong parameters)
  3. Use template literals (dùng template literals)
  4. Replace loops với array methods (thay vòng lặp bằng array methods)
  5. Make code concise và readable (làm code ngắn gọn và dễ đọc)
💡 Solution
javascript
// ✅ Solution:

// 1. getUserInfo with destructuring & template literal
const getUserInfo = ({ name, email, address: { city, country } }) =>
  `Name: ${name}
Email: ${email}
Location: ${city}, ${country}`;

// Test
console.log(getUserInfo(users[0]));

// 2. filterByAge with arrow function & filter
const filterByAge = (users, minAge) =>
  users.filter((user) => user.age >= minAge);

// Or with destructuring:
const filterByAge = (users, minAge) => users.filter(({ age }) => age >= minAge);

// Test
console.log(filterByAge(users, 28));

// 3. getEmails with map
const getEmails = (users) => users.map((user) => user.email);

// Or with destructuring:
const getEmails = (users) => users.map(({ email }) => email);

// Test
console.log(getEmails(users));

// BONUS: Combine everything
const getUserSummaries = (users, minAge) =>
  users
    .filter(({ age }) => age >= minAge)
    .map(({ name, email, address: { city } }) => ({
      name,
      email,
      city,
    }));

console.log(getUserSummaries(users, 28));

💪 Exercise 3: Shopping Cart System (⭐⭐⭐) - 40 phút

Requirement: Build complete shopping cart với modern JS.

javascript
// Products database
const products = [
  { id: 1, name: 'Laptop', price: 1200, category: 'electronics', stock: 5 },
  { id: 2, name: 'Mouse', price: 25, category: 'electronics', stock: 50 },
  { id: 3, name: 'Keyboard', price: 75, category: 'electronics', stock: 30 },
  { id: 4, name: 'Monitor', price: 300, category: 'electronics', stock: 15 },
  { id: 5, name: 'Desk', price: 450, category: 'furniture', stock: 8 },
  { id: 6, name: 'Chair', price: 250, category: 'furniture', stock: 12 },
];

const discountCodes = {
  SAVE10: 0.1,
  SAVE20: 0.2,
  SUMMER: 0.15,
};

// Starter cart
let cart = [];

Your Task: Implement các functions sau sử dụng ES6+:

  1. addItem(cart, productId, quantity) - Add/update item

  2. removeItem(cart, productId) - Remove item

  3. updateQuantity(cart, productId, quantity) - Update quantity

  4. calculateSubtotal(cart, products) - Total before tax

  5. calculateTax(subtotal, taxRate) - Calculate tax

  6. calculateTotal(cart, products, taxRate) - Final total

  7. getCartSummary(cart, products) - Detailed summary.

    Ouput: { items, itemCount, subtotal, tax, total }

  8. applyDiscount(total, discountCode) - Apply discount codes.

    Ouput: { total, discount, message }

  9. checkStock(cart, products) - Verify availability (kiểm tra tồn kho).

    Ouput: { allAvailable, outOfStock:[] }

  10. suggestRelated(cart, products, limit) - Suggest items (gợi ý danh sách sản phẩm, cùng categories nhưng không trong cart)

Rules:

  • ❌ NO mutation of original arrays/objects (không thay đổi trực tiếp mảng/object gốc)
  • ✅ Use spread operator for immutable updates (dùng spread operator để cập nhật bất biến)
  • ✅ Use array methods (map, filter, reduce) (dùng các array methods)
  • ✅ Use destructuring where appropriate (dùng destructuring khi phù hợp)
  • ✅ Use arrow functions (dùng arrow functions)
  • ✅ Handle edge cases (xử lý các edge cases - bug ẩn, hiếm gặp)
💡 Solution
javascript
// ✅ Complete Solution:

// 1. Add or update item in cart (immutably)
const addItem = (cart, productId, quantity = 1) => {
  const existingIndex = cart.findIndex((item) => item.productId === productId);

  if (existingIndex !== -1) {
    // Update existing item
    return cart.map((item, index) =>
      index === existingIndex
        ? { ...item, quantity: item.quantity + quantity }
        : item
    );
  }

  // Add new item
  return [...cart, { productId, quantity }];
};

// 2. Remove item from cart
const removeItem = (cart, productId) =>
  cart.filter((item) => item.productId !== productId);

// 3. Update quantity (remove if 0 or negative)
const updateQuantity = (cart, productId, quantity) => {
  if (quantity <= 0) {
    return removeItem(cart, productId);
  }

  return cart.map((item) =>
    item.productId === productId ? { ...item, quantity } : item
  );
};

// 4. Calculate subtotal
const calculateSubtotal = (cart, products) =>
  cart.reduce((total, item) => {
    const product = products.find((p) => p.id === item.productId);
    return total + (product ? product.price * item.quantity : 0);
  }, 0);

// 5. Calculate tax
const calculateTax = (subtotal, taxRate = 0.1) => subtotal * taxRate;

// 6. Calculate final total
const calculateTotal = (cart, products, taxRate = 0.1) => {
  const subtotal = calculateSubtotal(cart, products);
  const tax = calculateTax(subtotal, taxRate);
  return subtotal + tax;
};

// 7. Get detailed cart summary
const getCartSummary = (cart, products) => {
  const items = cart.map(({ productId, quantity }) => {
    const product = products.find((p) => p.id === productId);
    return {
      ...product,
      quantity,
      subtotal: product.price * quantity,
    };
  });

  const subtotal = items.reduce((sum, item) => sum + item.subtotal, 0);
  const tax = calculateTax(subtotal);
  const total = subtotal + tax;

  return {
    items,
    itemCount: cart.reduce((sum, item) => sum + item.quantity, 0),
    subtotal,
    tax,
    total,
  };
};

// 8. Apply discount codes
const discountCodes = {
  SAVE10: 0.1,
  SAVE20: 0.2,
  SUMMER: 0.15,
};

const applyDiscount = (total, code) => {
  const discount = discountCodes[code.toUpperCase()];
  if (!discount) {
    return { total, discount: 0, message: 'Invalid code' };
  }

  const discountAmount = total * discount;
  return {
    total: total - discountAmount,
    discount: discountAmount,
    message: `${discount * 100}% discount applied`,
  };
};

// 9. Check stock availability
const checkStock = (cart, products) => {
  const outOfStock = cart
    .map(({ productId, quantity }) => {
      const product = products.find((p) => p.id === productId);
      return {
        productId,
        productName: product.name,
        requested: quantity,
        available: product.stock,
        sufficient: product.stock >= quantity,
      };
    })
    .filter((item) => !item.sufficient);

  return {
    allAvailable: outOfStock.length === 0,
    outOfStock,
  };
};

// 10. Suggest related items (same category, not in cart)
const suggestRelated = (cart, products, limit = 3) => {
  // Lấy danh sách productId của các item trong giỏ hàng
  const cartProductIds = cart.map((item) => item.productId);

  // Lấy danh sách category của các sản phẩm trong giỏ
  // Cách 1: Dùng indexOf như bài giảng bên trên
  const cartCategories = cart
    .map(({ productId }) => products.find((p) => p.id === productId)?.category)
    .filter(
      // self ở đây chính là tham số thứ 3 (array) trong callbackFn của hàm filter,
      // Rename lại đặt tên là self cho dễ hiểu, dùng để kiểm tra trùng lặp.
      (category, index, self) => category && self.indexOf(category) === index
    ); //Loại bỏ category bị undefined/null và trùng nhau

  /* Cách 2: Dùng `Set` kiểu hiện đại, ngắn gọn dễ hiểu để xử lý trùng lặp
   *
   * const cartCategories = [
   * ...new Set(
   *       cart.map(({ productId }) => products.find((p) => p.id === productId)?.category
   *       .filter(Boolean)
   *  )
   *   ];
   */

  return (
    products
      .filter(
        (product) =>
          // Không nằm trong giỏ hàng
          !cartProductIds.includes(product.id) &&
          // Có category trùng với category trong giỏ
          cartCategories.includes(product.category)
      )
      // Giới hạn số lượng sản phẩm được gợi ý
      .slice(0, limit)
  );
};

// ═══════════════════════════════════════════════════════════
// TESTING
// ═══════════════════════════════════════════════════════════

// Build a cart
let myCart = [];
myCart = addItem(myCart, 1, 1); // Laptop
myCart = addItem(myCart, 2, 2); // 2x Mouse
myCart = addItem(myCart, 5, 1); // Desk

console.log('Cart:', myCart);

// Get summary
const summary = getCartSummary(myCart, products);
console.log('Summary:', summary);

// Check stock
const stockCheck = checkStock(myCart, products);
console.log('Stock Check:', stockCheck);

// Calculate total with discount
const total = calculateTotal(myCart, products, 0.1);
const withDiscount = applyDiscount(total, 'SAVE20');
console.log('Total:', total);
console.log('With Discount:', withDiscount);

// Get suggestions
const suggestions = suggestRelated(myCart, products);
console.log('Suggested Products:', suggestions);

// Update quantity
myCart = updateQuantity(myCart, 2, 5); // Update mouse to 5
console.log('Updated Cart:', myCart);

// Remove item
myCart = removeItem(myCart, 5); // Remove desk
console.log('After Removal:', myCart);

Testing Checklist:

  • [ ] Adding items works (thêm item hoạt động đúng)
  • [ ] Updating quantity works (cập nhật số lượng hoạt động đúng)
  • [ ] Removing items works (xoá item hoạt động đúng)
  • [ ] Subtotal calculates correctly (tính subtotal đúng)
  • [ ] Tax calculates correctly (tính thuế đúng)
  • [ ] Discount codes work (mã giảm giá hoạt động đúng)
  • [ ] Stock checking works (kiểm tra tồn kho hoạt động đúng)
  • [ ] Suggestions show related items (gợi ý sản phẩm liên quan)
  • [ ] No mutations happen (không có mutation xảy ra)

💪 Exercise 4: Data Analytics Pipeline (⭐⭐⭐⭐) - 60 phút

Requirement: Process và analyze e-commerce data.

javascript
// Sales data from API
const salesData = [
  {
    orderId: 1001,
    customerId: 'C001',
    date: '2025-01-01',
    items: [
      {
        productId: 'P001',
        name: 'Laptop',
        price: 1200,
        quantity: 1,
        category: 'electronics',
      },
      {
        productId: 'P002',
        name: 'Mouse',
        price: 25,
        quantity: 2,
        category: 'electronics',
      },
    ],
    status: 'completed',
    shippingCost: 15,
  },
  {
    orderId: 1002,
    customerId: 'C002',
    date: '2025-01-01',
    items: [
      {
        productId: 'P003',
        name: 'Desk',
        price: 450,
        quantity: 1,
        category: 'furniture',
      },
    ],
    status: 'completed',
    shippingCost: 50,
  },
  {
    orderId: 1003,
    customerId: 'C001',
    date: '2025-01-02',
    items: [
      {
        productId: 'P004',
        name: 'Chair',
        price: 250,
        quantity: 2,
        category: 'furniture',
      },
      {
        productId: 'P002',
        name: 'Mouse',
        price: 25,
        quantity: 1,
        category: 'electronics',
      },
    ],
    status: 'pending',
    shippingCost: 40,
  },
  {
    orderId: 1004,
    customerId: 'C003',
    date: '2025-01-02',
    items: [
      {
        productId: 'P001',
        name: 'Laptop',
        price: 1200,
        quantity: 1,
        category: 'electronics',
      },
      {
        productId: 'P005',
        name: 'Monitor',
        price: 300,
        quantity: 1,
        category: 'electronics',
      },
    ],
    status: 'completed',
    shippingCost: 20,
  },
  {
    orderId: 1005,
    customerId: 'C002',
    date: '2025-01-03',
    items: [
      {
        productId: 'P003',
        name: 'Desk',
        price: 450,
        quantity: 1,
        category: 'furniture',
      },
      {
        productId: 'P004',
        name: 'Chair',
        price: 250,
        quantity: 1,
        category: 'furniture',
      },
    ],
    status: 'completed',
    shippingCost: 60,
  },
];

//Giá cost của sản phẩm
const productCosts = {
  P001: 800, // Laptop
  P002: 15, // Mouse
  P003: 300, // Desk
  P004: 150, // Chair
  P005: 200, // Monitor
};

Your Task: Implement analytics functions:

  1. getTotalRevenue(data) - Total from all completed orders
  2. getRevenueByDate(data) - Revenue grouped by date
  3. getRevenueByCategory(data) - Revenue by product category
  4. getTopProducts(data, limit) - Best selling products
  5. getCustomerStats(data) - Customer purchase statistics
  6. getAverageOrderValue(data) - AOV for completed orders (Giá trị đơn hàng trung bình (AOV) của các đơn đã hoàn thành)
  7. getProductPerformance(data) - Detailed product metrics (Các chỉ số chi tiết về hiệu suất sản phẩm)
  8. getDailySummary(data) - Complete daily breakdown (Báo cáo tổng hợp theo ngày)
  9. getCustomerSegments(data) - Segment customers by spend (Phân nhóm khách hàng theo mức chi tiêu)
  10. getProfitMargins(data, costs) - Calculate profit margins (Tính biên lợi nhuận, giá vốn so với giá bán )

Advanced Requirements:

  • Handle incomplete data gracefully (Xử lý dữ liệu thiếu hoặc không đầy đủ một cách an toàn)
  • Support filtering by date range
  • Support filtering by status
  • Calculate percentage changes
  • Format currency properly (Định dạng tiền tệ đúng chuẩn)
💡 Solution
javascript
// ✅ Complete Analytics Solution:

// Helper: Calculate order total
const getOrderTotal = ({ items, shippingCost = 0 }) =>
  items.reduce((sum, item) => sum + (item.price * item.quantity), 0) + shippingCost;

// 1. Total revenue from completed orders
const getTotalRevenue = (data) =>
  data
    .filter(order => order.status === 'completed')
    .reduce((total, order) => total + getOrderTotal(order), 0);
    // Hoặc gọi getOrderTotal({order.items, order.shippingCost})

console.log(`Total Revenue: $${getTotalRevenue(salesData)}`);

// 2. Revenue grouped by date
const getRevenueByDate = (data) => {
  const completed = data.filter(order => order.status === 'completed');

  return completed.reduce((acc, order) => {
    const { date } = order;
    const total = getOrderTotal(order);

    acc[date] = (acc[date] || 0) + total;
    return acc;
  }, {});
};

console.log('Revenue by Date:', getRevenueByDate(salesData));

// 3. Revenue by category
const getRevenueByCategory = (data) => {
  const completed = data.filter(order => order.status === 'completed');

  return completed.reduce((acc, order) => {
    order.items.forEach(({ category, price, quantity }) => {
      acc[category] = (acc[category] || 0) + (price * quantity);
    });
    return acc;
  }, {});
};

console.log('Revenue by Category:', getRevenueByCategory(salesData));

// 4. Top selling products
const getTopProducts = (data, limit = 5) => {
  const completed = data.filter(order => order.status === 'completed');

  // Aggregate product sales
  const productSales = completed.reduce((acc, order) => {
    order.items.forEach(({ productId, name, price, quantity, category }) => {
      if (!acc[productId]) {
        acc[productId] = { productId, name, category, totalQuantity: 0, totalRevenue: 0 };
      }
      acc[productId].totalQuantity += quantity;
      acc[productId].totalRevenue += price * quantity;
    });
    return acc;
  }, {});

  // Sort and limit
  return Object.values(productSales)
    .sort((a, b) => b.totalRevenue - a.totalRevenue)
    .slice(0, limit)
    .map((product, index) => ({
      rank: index + 1,
      ...product
    }));
};

console.log('Top Products:', getTopProducts(salesData, 3));

// 5. Customer statistics
const getCustomerStats = (data) => {
  const byCustomer = data.reduce((acc, order) => {
    const { customerId, status } = order;
    const total = getOrderTotal(order);

    if (!acc[customerId]) {
      acc[customerId] = {
        customerId,
        totalOrders: 0,
        completedOrders: 0,
        pendingOrders: 0,
        totalSpent: 0
      };
    }

    acc[customerId].totalOrders++;
    if (status === 'completed') {
      acc[customerId].completedOrders++;
      acc[customerId].totalSpent += total;
    } else if (status === 'pending') {
      acc[customerId].pendingOrders++;
    }

    return acc;
  }, {});

  return Object.values(byCustomer).map(customer => ({
    ...customer,
    averageOrderValue: customer.completedOrders > 0
      ? customer.totalSpent / customer.completedOrders
      : 0
  }));
};

console.log('Customer Stats:', getCustomerStats(salesData));

// 6. Average Order Value
const getAverageOrderValue = (data) => {
  const completed = data.filter(order => order.status === 'completed');
  if (completed.length === 0) return 0;

  const total = completed.reduce((sum, order) => sum + getOrderTotal(order), 0);
  return total / completed.length;
};

console.log(`Average Order Value: $${getAverageOrderValue(salesData).toFixed(2)}`);

// 7. Product performance metrics
const getProductPerformance = (data) => {
  const completed = data.filter(order => order.status === 'completed');

  const performance = completed.reduce((acc, order) => {
    order.items.forEach(({ productId, name, price, quantity, category }) => {
      if (!acc[productId]) {
        acc[productId] = {
          productId,
          name,
          category,
          unitsSold: 0,
          revenue: 0,
          orderCount: 0
        };
      }

      acc[productId].unitsSold += quantity;
      acc[productId].revenue += price * quantity;
      acc[productId].orderCount++;
    });
    return acc;
  }, {});

  return Object.values(performance).map(product => ({
    ...product,
    averageUnitsPerOrder: product.unitsSold / product.orderCount,
    averageRevenue PerOrder: product.revenue / product.orderCount
  }));
};

console.log('Product Performance:', getProductPerformance(salesData));

// 8. Daily summary
const getDailySummary = (data) => {
  const byDate = data.reduce((acc, order) => {
    const { date, status } = order;

    if (!acc[date]) {
      acc[date] = {
        date,
        totalOrders: 0,
        completedOrders: 0,
        pendingOrders: 0,
        revenue: 0,
        items: []
      };
    }

    acc[date].totalOrders++;
    if (status === 'completed') {
      acc[date].completedOrders++;
      acc[date].revenue += getOrderTotal(order);
    } else if (status === 'pending') {
      acc[date].pendingOrders++;
    }

    acc[date].items.push(...order.items.map(item => item.productId));

    return acc;
  }, {});

  return Object.values(byDate).map(day => ({
    ...day,
    uniqueProducts: [...new Set(day.items)].length,
    averageOrderValue: day.completedOrders > 0
      ? day.revenue / day.completedOrders
      : 0,
    items: undefined // Remove items array
  }));
};

console.log('Daily Summary:', getDailySummary(salesData));

// 9. Customer segments
const getCustomerSegments = (data) => {
  const stats = getCustomerStats(data);

  //Phân khúc khách hàng theo chi tiêu
  const segments = {
    high: { customers: [], threshold: 1000, totalSpent: 0 },
    medium: { customers: [], threshold: 500, totalSpent: 0 },
    low: { customers: [], totalSpent: 0 }
  };
  const { high, medium, low } = segments
  stats.forEach(customer => {
    const { totalSpent } = customer;

    if (totalSpent >= high.threshold) {
      high.customers.push(customer);
      high.totalSpent += totalSpent;
    } else if (totalSpent >= medium.threshold) {
      medium.customers.push(customer);
      medium.totalSpent += totalSpent;
    } else {
      low.customers.push(customer);
      low.totalSpent += totalSpent;
    }
  });

  return {
    high: {
      count: high.customers.length,
      totalRevenue: high.totalSpent,
      averageSpend: high.customers.length > 0
        ? high.totalSpent / high.customers.length
        : 0
    },
    medium: {
      count: medium.customers.length,
      totalRevenue: medium.totalSpent,
      averageSpend: medium.customers.length > 0
        ? medium.totalSpent / medium.customers.length
        : 0
    },
    low: {
      count: low.customers.length,
      totalRevenue: low.totalSpent,
      averageSpend: low.customers.length > 0
        ? low.totalSpent / low.customers.length
        : 0
    }
  };
};

console.log('Customer Segments:', getCustomerSegments(salesData));

// 10. Profit margins (assuming cost data)
const getProfitMargins = (data, costs) => {
  const performance = getProductPerformance(data);

  return performance.map(product => {
    const { productId, unitsSold, revenue } = product;
    const cost = costs[productId] || 0;
    const totalCost = cost * unitsSold;
    const profit = revenue - totalCost;
    const marginPercent = revenue > 0
      ? (profit / revenue) * 100
      : 0;

    return {
      ...product,
      totalCost,
      profit,
      marginPercent: marginPercent.toFixed(2) + '%'
    };
  }).sort((a, b) => parseFloat(b.marginPercent) - parseFloat(a.marginPercent));
};

console.log('Profit Margins:', getProfitMargins(salesData, productCosts));

Complete Dashboard Output:

javascript
const generateDashboard = (data) => {
  const revenue = getTotalRevenue(data);
  const aov = getAverageOrderValue(data);
  const topProducts = getTopProducts(data, 3);
  const segments = getCustomerSegments(data);
  const daily = getDailySummary(data);

  return {
    overview: {
      totalRevenue: `$${revenue.toFixed(2)}`,
      averageOrderValue: `$${aov.toFixed(2)}`,
      totalOrders: data.filter((o) => o.status === 'completed').length,
    },
    topProducts,
    customerSegments: segments,
    dailyBreakdown: daily,
  };
};

console.log('=== DASHBOARD ===');
console.log(JSON.stringify(generateDashboard(salesData), null, 2));

💪 Exercise 5: Production-Ready Utility Library (⭐⭐⭐⭐⭐) - 90 phút

Requirement: Xây dựng reusable utility library với modern JS.

Your Task: Tạo utils.js library với:

Array Utilities:

  1. chunk(array, size) - Chia array thành các chunks
  2. unique(array) - Loại bỏ duplicates
  3. groupBy(array, key) - Group theo property
  4. sortBy(array, key) - Sort theo property
  5. pluck(array, key) - Extract giá trị của property

Object Utilities:

  1. pick(object, keys) - Pick các keys cụ thể
  2. omit(object, keys) - Omit các keys cụ thể
  3. merge(objects...) - Deep merge objects
  4. flatten(object) - Flatten nested object
  5. invert(object) - Swap keys và values

Function Utilities:

  1. pipe(...fns) - Compose functions theo thứ tự left-to-right
  2. compose(...fns) - Compose functions theo thứ tự right-to-left
  3. debounce(fn, delay) - Debounce function
  4. throttle(fn, delay) - Throttle function
  5. memoize(fn) - Cache function results

Requirements:

  • All functions must be pure (no side effects)(Tất cả functions phải là pure (không có side effects))
  • Handle edge cases
  • Add JSDoc comments
  • Include usage examples
  • Write test cases
💡 Solution
javascript
// ✅ Production-Ready Utility Library

/**
 * utils.js - Modern JavaScript Utility Library
 * All functions are pure and immutable
 */

// ═══════════════════════════════════════════════════════════
// ARRAY UTILITIES
// ═══════════════════════════════════════════════════════════

/**
 * Split array into chunks of specified size
 * @param {Array} array - Input array
 * @param {number} size - Chunk size
 * @returns {Array} Array of chunks
 */
const chunk = (array, size) => {
  if (!Array.isArray(array) || size <= 0) return [];

  return array.reduce((chunks, item, index) => {
    const chunkIndex = Math.floor(index / size);
    chunks[chunkIndex] = chunks[chunkIndex] || [];
    chunks[chunkIndex].push(item);
    return chunks;
  }, []);
};

// Usage
console.log(chunk([1, 2, 3, 4, 5, 6, 7], 3));
// [[1, 2, 3], [4, 5, 6], [7]]

/**
 * Remove duplicate values from array
 * @param {Array} array - Input array
 * @returns {Array} Array with unique values
 */
const unique = (array) => {
  if (!Array.isArray(array)) return [];
  return [...new Set(array)];
};

// Usage
console.log(unique([1, 2, 2, 3, 3, 3, 4]));
// [1, 2, 3, 4]

/**
 * Group array items by key
 * @param {Array} array - Input array
 * @param {string|Function} key - Property name or function
 * @returns {Object} Grouped object
 */
const groupBy = (array, key) => {
  if (!Array.isArray(array)) return {};

  return array.reduce((groups, item) => {
    const groupKey = typeof key === 'function' ? key(item) : item[key];
    groups[groupKey] = groups[groupKey] || [];
    groups[groupKey].push(item);
    return groups;
  }, {});
};

// Usage
const users = [
  { name: 'John', role: 'admin' },
  { name: 'Jane', role: 'user' },
  { name: 'Bob', role: 'admin' },
];
console.log(groupBy(users, 'role'));
// { admin: [...], user: [...] }

/**
 * Sort array by key
 * @param {Array} array - Input array
 * @param {string|Function} key - Property or comparator
 * @param {string} order - 'asc' or 'desc'
 * @returns {Array} Sorted array (new copy)
 */
const sortBy = (array, key, order = 'asc') => {
  if (!Array.isArray(array)) return [];

  const sorted = [...array].sort((a, b) => {
    const aVal = typeof key === 'function' ? key(a) : a[key];
    const bVal = typeof key === 'function' ? key(b) : b[key];

    if (aVal < bVal) return order === 'asc' ? -1 : 1;
    if (aVal > bVal) return order === 'asc' ? 1 : -1;
    return 0;
  });

  return sorted;
};

// Usage
console.log(sortBy(users, 'name', 'asc'));

/**
 * Extract property values from array of objects
 * @param {Array} array - Input array
 * @param {string} key - Property name
 * @returns {Array} Array of values
 */
const pluck = (array, key) => {
  if (!Array.isArray(array)) return [];
  return array.map((item) => item?.[key]);
};

// Usage
console.log(pluck(users, 'name'));
// ['John', 'Jane', 'Bob']

// ═══════════════════════════════════════════════════════════
// OBJECT UTILITIES
// ═══════════════════════════════════════════════════════════

/**
 * Pick specific keys from object
 * @param {Object} object - Input object
 * @param {Array<string>} keys - Keys to pick
 * @returns {Object} New object with picked keys
 */
const pick = (object, keys) => {
  if (!object || typeof object !== 'object') return {};

  return keys.reduce((result, key) => {
    if (key in object) {
      result[key] = object[key];
    }
    return result;
  }, {});
};

// Usage
const user = {
  name: 'John',
  age: 25,
  email: 'john@example.com',
  password: 'secret',
};
console.log(pick(user, ['name', 'email']));
// { name: 'John', email: 'john@example.com' }

/**
 * Omit specific keys from object
 * @param {Object} object - Input object
 * @param {Array<string>} keys - Keys to omit
 * @returns {Object} New object without omitted keys
 */
const omit = (object, keys) => {
  if (!object || typeof object !== 'object') return {};

  return Object.keys(object).reduce((result, key) => {
    if (!keys.includes(key)) {
      result[key] = object[key];
    }
    return result;
  }, {});
};

// Usage
console.log(omit(user, ['password']));
// { name: 'John', age: 25, email: 'john@example.com' }

/**
 * Deep merge objects
 * @param {...Object} objects - Objects to merge
 * @returns {Object} Merged object
 */
const merge = (...objects) => {
  const isObject = (obj) =>
    obj && typeof obj === 'object' && !Array.isArray(obj);

  return objects.reduce((result, obj) => {
    Object.keys(obj).forEach((key) => {
      if (isObject(obj[key]) && isObject(result[key])) {
        result[key] = merge(result[key], obj[key]);
      } else {
        result[key] = obj[key];
      }
    });
    return result;
  }, {});
};

// Usage
const obj1 = { a: 1, b: { c: 2 } };
const obj2 = { b: { d: 3 }, e: 4 };
console.log(merge(obj1, obj2));
// { a: 1, b: { c: 2, d: 3 }, e: 4 }

/**
 * Flatten nested object
 * @param {Object} object - Input object
 * @param {string} separator - Key separator
 * @returns {Object} Flattened object
 */
const flatten = (object, separator = '.') => {
  const isObject = (obj) =>
    obj && typeof obj === 'object' && !Array.isArray(obj);

  const flatten = (obj, prefix = '') => {
    return Object.keys(obj).reduce((acc, key) => {
      const newKey = prefix ? `${prefix}${separator}${key}` : key;

      if (isObject(obj[key])) {
        Object.assign(acc, flatten(obj[key], newKey));
      } else {
        acc[newKey] = obj[key];
      }

      return acc;
    }, {});
  };

  return flatten(object);
};

// Usage
const nested = { a: 1, b: { c: 2, d: { e: 3 } } };
console.log(flatten(nested));
// { 'a': 1, 'b.c': 2, 'b.d.e': 3 }

/**
 * Invert object (swap keys and values)
 * @param {Object} object - Input object
 * @returns {Object} Inverted object
 */
const invert = (object) => {
  if (!object || typeof object !== 'object') return {};

  return Object.keys(object).reduce((result, key) => {
    result[object[key]] = key;
    return result;
  }, {});
};

// Usage
console.log(invert({ a: '1', b: '2', c: '3' }));
// { '1': 'a', '2': 'b', '3': 'c' }

// ═══════════════════════════════════════════════════════════
// FUNCTION UTILITIES
// ═══════════════════════════════════════════════════════════

/**
 * Compose functions left-to-right
 * @param {...Function} fns - Functions to compose
 * @returns {Function} Composed function
 */
const pipe =
  (...fns) =>
  (value) =>
    fns.reduce((acc, fn) => fn(acc), value);

// Usage
const add5 = (x) => x + 5;
const multiply3 = (x) => x * 3;
const subtract2 = (x) => x - 2;

const calculate = pipe(add5, multiply3, subtract2);
console.log(calculate(10)); // ((10 + 5) * 3) - 2 = 43

/**
 * Compose functions right-to-left
 * @param {...Function} fns - Functions to compose
 * @returns {Function} Composed function
 */
const compose =
  (...fns) =>
  (value) =>
    fns.reduceRight((acc, fn) => fn(acc), value);

// Usage
const calculate2 = compose(subtract2, multiply3, add5);
console.log(calculate2(10)); // Same as pipe

/**
 * Debounce function (call after delay of inactivity)
 * @param {Function} fn - Function to debounce
 * @param {number} delay - Delay in milliseconds
 * @returns {Function} Debounced function
 */
const debounce = (fn, delay) => {
  let timeoutId;

  return (...args) => {
    clearTimeout(timeoutId);
    timeoutId = setTimeout(() => fn(...args), delay);
  };
};

// Usage
const searchAPI = debounce((query) => {
  console.log('Searching for:', query);
}, 500);

// Only calls after 500ms of no input
searchAPI('hello');
searchAPI('hello world'); // Only this one executes

/**
 * Throttle function (call at most once per delay)
 * @param {Function} fn - Function to throttle
 * @param {number} delay - Minimum delay between calls
 * @returns {Function} Throttled function
 */
const throttle = (fn, delay) => {
  let lastCall = 0;

  return (...args) => {
    const now = Date.now();

    if (now - lastCall >= delay) {
      lastCall = now;
      fn(...args);
    }
  };
};

// Usage
const logScroll = throttle(() => {
  console.log('Scrolled!');
}, 1000);

// Calls at most once per second
window.addEventListener('scroll', logScroll);

/**
 * Memoize function (cache results)
 * @param {Function} fn - Function to memoize
 * @returns {Function} Memoized function
 */
const memoize = (fn) => {
  const cache = new Map();

  return (...args) => {
    const key = JSON.stringify(args);

    if (cache.has(key)) {
      console.log('Cache hit!');
      return cache.get(key);
    }

    const result = fn(...args);
    cache.set(key, result);
    return result;
  };
};

// Usage
const expensiveCalculation = (n) => {
  console.log('Calculating...');
  return n * n;
};

const memoized = memoize(expensiveCalculation);
console.log(memoized(5)); // Calculating... 25
console.log(memoized(5)); // Cache hit! 25 (instant)

// ═══════════════════════════════════════════════════════════
// EXPORT (for use in other files)
// ═══════════════════════════════════════════════════════════

export {
  // Array
  chunk,
  unique,
  groupBy,
  sortBy,
  pluck,
  // Object
  pick,
  omit,
  merge,
  flatten,
  invert,
  // Function
  pipe,
  compose,
  debounce,
  throttle,
  memoize,
};

Test Suite:

javascript
// utils.test.js
console.log('Running tests...\n');

// Test chunk
console.assert(
  JSON.stringify(chunk([1, 2, 3, 4, 5], 2)) ===
    JSON.stringify([[1, 2], [3, 4], [5]]),
  'chunk failed'
);

// Test unique
console.assert(
  JSON.stringify(unique([1, 1, 2, 2, 3])) === JSON.stringify([1, 2, 3]),
  'unique failed'
);

// Test groupBy
const items = [
  { type: 'a', val: 1 },
  { type: 'b', val: 2 },
  { type: 'a', val: 3 },
];
const grouped = groupBy(items, 'type');
console.assert(grouped.a.length === 2, 'groupBy failed');

// Test pick
const obj = { a: 1, b: 2, c: 3 };
console.assert(
  JSON.stringify(pick(obj, ['a', 'c'])) === JSON.stringify({ a: 1, c: 3 }),
  'pick failed'
);

// Test pipe
const result = pipe(
  (x) => x + 1,
  (x) => x * 2
)(5);
console.assert(result === 12, 'pipe failed');

console.log('All tests passed! ✓');

VI. DEBUG LAB (20 phút)

🐛 Bug 1: Scope Issue

javascript
// This code has a scope bug. Find and fix it.

function createCounters() {
  var counters = [];

  for (var i = 0; i < 3; i++) {
    counters.push(function () {
      console.log(i);
    });
  }

  return counters;
}

const counters = createCounters();
counters[0](); // Expected: 0, Actual: 3
counters[1](); // Expected: 1, Actual: 3
counters[2](); // Expected: 2, Actual: 3
💡 Solution & Explanation
javascript
// Problem: `var` có function-scoped, nên tất cả functions dùng chung cùng một `i`
// Khi các functions được thực thi, i đã là 3 (kết thúc vòng lặp)

// ✅ Solution 1: Dùng `let` (block-scoped)
function createCounters() {
  const counters = [];

  for (let i = 0; i < 3; i++) {
    // let tạo binding mới cho mỗi iteration
    counters.push(function () {
      console.log(i);
    });
  }

  return counters;
}

// ✅ Solution 2: Dùng closure (nếu buộc phải dùng var)
function createCounters() {
  var counters = [];

  for (var i = 0; i < 3; i++) {
    counters.push(
      (function (j) {
        return function () {
          console.log(j);
        };
      })(i)
    ); // IIFE capture giá trị i hiện tại
  }

  return counters;
}

// ✅ Solution 3: Cách hiện đại với map
function createCounters() {
  return [0, 1, 2].map((i) => () => console.log(i));
}

🐛 Bug 2: Vấn đề this Binding

javascript
// Đoạn code này bị mất context. Tìm và sửa nó.

const user = {
  name: 'John',
  friends: ['Jane', 'Bob', 'Alice'],

  greetFriends: function () {
    this.friends.forEach(function (friend) {
      console.log(this.name + ' says hi to ' + friend);
      // TypeError: Cannot read property 'name' of undefined (Không thể đọc thuộc tính 'name' của undefined)
    });
  },
};

user.greetFriends();
💡 Solution & Explanation
javascript
// Vấn đề: Regular function trong forEach làm mất context của `this`

// ✅ Solution 1: Arrow function (giữ nguyên this)
const user = {
  name: 'John',
  friends: ['Jane', 'Bob', 'Alice'],

  greetFriends: function () {
    this.friends.forEach((friend) => {
      // Arrow function!
      console.log(`${this.name} says hi to ${friend}`);
    });
  },
};

// ✅ Solution 2: Bind this
const user = {
  name: 'John',
  friends: ['Jane', 'Bob', 'Alice'],

  greetFriends: function () {
    this.friends.forEach(
      function (friend) {
        console.log(`${this.name} says hi to ${friend}`);
      }.bind(this)
    ); // Bind this một cách tường minh
  },
};

// ✅ Solution 3: Lưu reference của this
const user = {
  name: 'John',
  friends: ['Jane', 'Bob', 'Alice'],

  greetFriends: function () {
    const self = this; // Lưu reference
    this.friends.forEach(function (friend) {
      console.log(`${self.name} says hi to ${friend}`);
    });
  },
};

🐛 Bug 3: Lỗi Mutation

javascript
// Đoạn code này mutate dữ liệu gốc. Hãy tìm và sửa lỗi.

const originalUsers = [
  { id: 1, name: 'John', active: false },
  { id: 2, name: 'Jane', active: false },
];

function activateUsers(users) {
  return users.map((user) => {
    user.active = true; // BUG: Mutating dữ liệu gốc!
    return user;
  });
}

const activeUsers = activateUsers(originalUsers);
console.log(originalUsers[0].active); // true (lẽ ra phải là false!)
💡 Solution & Explanation
javascript
// Vấn đề: map trả về array mới nhưng các object bên trong vẫn là cùng reference

// ✅ Giải pháp: Tạo object mới
function activateUsers(users) {
  return users.map((user) => ({
    ...user, // Spread tạo object mới
    active: true,
  }));
}

// Bây giờ originalUsers không bị thay đổi
const activeUsers = activateUsers(originalUsers);
console.log(originalUsers[0].active); // false ✓
console.log(activeUsers[0].active); // true ✓

// ⚠️ Lưu ý: Spread là shallow!
// Với object lồng nhau, cần deep copy:
const deepCopy = (users) =>
  users.map((user) => ({
    ...user,
    address: { ...user.address }, // Nếu user có address lồng nhau
  }));

VII. INTERVIEW PREP (15 phút)

💼 Junior Level Questions

Q1: What's the difference between let, const, and var?

Expected Answer
  • var: Function-scoped, can be redeclared, hoisted
  • let: Block-scoped, cannot be redeclared, hoisted but not initialized
  • const: Block-scoped, cannot be reassigned (but objects/arrays can be mutated)
javascript
// var: function-scoped
function test() {
  if (true) {
    var x = 1;
  }
  console.log(x); // 1 (accessible outside block)
}

// let: block-scoped
function test() {
  if (true) {
    let x = 1;
  }
  console.log(x); // ReferenceError
}

// const: cannot reassign
const x = 1;
x = 2; // Error

const user = { name: 'John' };
user.name = 'Jane'; // OK (mutation)
user = {}; // Error (reassignment)

Q2: Explain arrow functions and when NOT to use them.

Expected Answer

Arrow functions:

  • Shorter syntax
  • Lexical this binding (inherit from surrounding scope)
  • Cannot be used as constructors
  • No arguments object

Don't use arrow functions for:

  • Object methods (loses this)
  • Event handlers that need this
  • Functions that need arguments
javascript
// ✅ Good uses:
const numbers = [1, 2, 3];
const doubled = numbers.map((x) => x * 2);

setTimeout(() => console.log('Hello'), 1000);

// ❌ Bad uses:
const person = {
  name: 'John',
  greet: () => console.log(this.name), // undefined!
};

button.addEventListener('click', () => {
  this.classList.toggle('active'); // this is not the button!
});

💼 Mid Level Questions

Q3: Explain the difference between map, filter, and reduce. When would you use each?

Expected Answer
  • map: Transform each item, returns new array (same length)
  • filter: Keep items that pass test, returns new array (≤ length)
  • reduce: Combine into single value

Use map when: Transforming data (API responses, formatting) Use filter when: Removing items (search, validation) Use reduce when: Aggregating (sums, grouping, flattening)

javascript
const numbers = [1, 2, 3, 4, 5];

// map: transform each
const doubled = numbers.map((x) => x * 2);
// [2, 4, 6, 8, 10]

// filter: keep some
const evens = numbers.filter((x) => x % 2 === 0);
// [2, 4]

// reduce: combine
const sum = numbers.reduce((acc, x) => acc + x, 0);
// 15

// Real use: Transform API response
const apiUsers = response.data.map((user) => ({
  id: user.user_id,
  name: `${user.first} ${user.last}`,
  email: user.email_address,
}));

💼 Senior Level Questions

Q4: Implement a groupBy function that groups array items by a key, then explain how reduce works internally.

Expected Answer
javascript
// Implementation
const groupBy = (array, key) => {
  return array.reduce((groups, item) => {
    // Get the grouping key
    const groupKey = typeof key === 'function' ? key(item) : item[key];

    // Initialize group if it doesn't exist
    if (!groups[groupKey]) {
      groups[groupKey] = [];
    }

    // Add item to group
    groups[groupKey].push(item);

    return groups;
  }, {});
};

// Usage
const users = [
  { name: 'John', role: 'admin', age: 25 },
  { name: 'Jane', role: 'user', age: 30 },
  { name: 'Bob', role: 'admin', age: 35 },
];

const byRole = groupBy(users, 'role');
// { admin: [{...}, {...}], user: [{...}] }

const byAgeGroup = groupBy(users, (user) =>
  user.age < 30 ? 'young' : 'senior'
);
// { young: [{...}], senior: [{...}, {...}] }

How reduce works internally:

  1. Takes accumulator (initial value) and current item
  2. Executes reducer function
  3. Returns new accumulator value
  4. Passes to next iteration
  5. Final accumulator is the result

Key points:

  • Accumulator can be any type (object, array, number, etc.)
  • Always return accumulator in each iteration
  • Initial value is crucial - wrong type causes bugs
  • Most powerful array method - can implement map/filter with reduce
javascript
// Implementing map with reduce
const map = (array, fn) =>
  array.reduce((result, item) => {
    result.push(fn(item));
    return result;
  }, []);

// Implementing filter with reduce
const filter = (array, predicate) =>
  array.reduce((result, item) => {
    if (predicate(item)) {
      result.push(item);
    }
    return result;
  }, []);

Q5: What are the performance implications of using spread operator vs Object.assign vs manual property assignment for object copying?

Expected Answer

Performance hierarchy (fastest → slowest):

  1. Manual assignment (fastest)
  2. Object.assign
  3. Spread operator (slowest)
javascript
// Benchmark example (conceptual)
const obj = { a: 1, b: 2, c: 3, d: 4, e: 5 };

// 1. Manual (fastest) ~1.0x
const manual = {
  a: obj.a,
  b: obj.b,
  c: obj.c,
  d: obj.d,
  e: obj.e,
  f: 6,
};

// 2. Object.assign (fast) ~1.2x
const assigned = Object.assign({}, obj, { f: 6 });

// 3. Spread (slower) ~1.5x
const spread = { ...obj, f: 6 };

Trade-offs:

MethodPerformanceReadabilityMaintainabilityUse Case
Manual⭐⭐⭐Performance-critical code
Object.assign⭐⭐⭐⭐⭐⭐Multiple source objects
Spread⭐⭐⭐⭐⭐⭐Modern React code

Real-world implications:

  • For <100 objects/sec: Use spread (readability wins)
  • For >1000 objects/sec: Consider Object.assign
  • For >10000 objects/sec: Profile and optimize

React context:

javascript
// State updates - spread is fine
setState((prevState) => ({
  ...prevState,
  count: prevState.count + 1,
}));

// Hot path rendering - might need optimization
// (But optimize only after profiling!)

Memory considerations:

  • All methods create new object (same memory cost)
  • Spread has slight overhead from transpilation
  • For large objects (>50 properties), consider immutability libraries

Q6: Design a data transformation pipeline using composition. Explain currying and partial application.

Expected Answer

Composition Pattern:

javascript
// Individual transformations
const normalize = (str) => str.toLowerCase().trim();
const removeSpaces = (str) => str.replace(/\s+/g, '-');
const addPrefix = (prefix) => (str) => `${prefix}-${str}`;
const addSuffix = (suffix) => (str) => `${str}-${suffix}`;

// Compose (right to left)
const compose =
  (...fns) =>
  (value) =>
    fns.reduceRight((acc, fn) => fn(acc), value);

// Pipe (left to right) - more intuitive
const pipe =
  (...fns) =>
  (value) =>
    fns.reduce((acc, fn) => fn(acc), value);

// Build transformation pipeline
const createSlug = pipe(
  normalize, // "Hello World" → "hello world"
  removeSpaces, // "hello world" → "hello-world"
  addPrefix('post'), // "hello-world" → "post-hello-world"
  addSuffix('2025') // "post-hello-world" → "post-hello-world-2025"
);

console.log(createSlug('  Hello World  '));
// "post-hello-world-2025"

Currying:

javascript
// Regular function
const add = (a, b, c) => a + b + c;
add(1, 2, 3); // 6

// Curried version
const addCurried = (a) => (b) => (c) => a + b + c;
addCurried(1)(2)(3); // 6

// Practical use: Create specialized functions
const add1 = addCurried(1);
const add1and2 = add1(2);
console.log(add1and2(3)); // 6

// Real-world example: Logger
const log = (level) => (message) => (timestamp) =>
  `[${timestamp}] ${level}: ${message}`;

const logError = log('ERROR');
const logErrorNow = logError('Something went wrong');
console.log(logErrorNow(new Date().toISOString()));

Partial Application:

javascript
// Partial application helper
const partial =
  (fn, ...presetArgs) =>
  (...laterArgs) =>
    fn(...presetArgs, ...laterArgs);

// Example: fetch wrapper
const fetchJSON = (method, url, data) =>
  fetch(url, {
    method,
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(data),
  }).then((r) => r.json());

// Create specialized versions
const get = partial(fetchJSON, 'GET');
const post = partial(fetchJSON, 'POST');
const put = partial(fetchJSON, 'PUT');

// Use them
get('/api/users');
post('/api/users', { name: 'John' });

Real-world Pipeline Example:

javascript
// Data transformation pipeline for API response
const transformUsers = pipe(
  // 1. Extract data array
  (response) => response.data,

  // 2. Filter active users
  (users) => users.filter((u) => u.active),

  // 3. Transform shape
  (users) =>
    users.map((u) => ({
      id: u.user_id,
      name: `${u.first_name} ${u.last_name}`,
      email: u.email_address,
    })),

  // 4. Sort by name
  (users) => users.sort((a, b) => a.name.localeCompare(b.name)),

  // 5. Add index
  (users) =>
    users.map((user, index) => ({
      ...user,
      displayOrder: index + 1,
    }))
);

// Use it
fetch('/api/users')
  .then((r) => r.json())
  .then(transformUsers)
  .then((users) => console.log(users));

Key Benefits:

  • Testable (each function is pure)
  • Reusable (compose in different ways)
  • Readable (declarative pipeline)
  • Maintainable (easy to add/remove steps)

VIII. CODE REVIEW CHECKLIST (15 phút)

📋 ES6+ Code Review Guide

Khi review code sử dụng ES6+, check các điểm sau:

✅ Variables & Scope

javascript
// ❌ KHÔNG TỐT
var count = 0;
if (true) {
  var count = 1; // Ghi đè biến count bên ngoài
}

// ✅ TỐT
let count = 0;
if (true) {
  let count = 1; // Phạm vi (scope) riêng biệt
}

// ✅ TỐT HƠN
const count = 0; // Dùng const khi không cần gán lại

Checklist:

  • [ ] Không sử dụng var
  • [ ] Dùng const cho các giá trị không thay đổi
  • [ ] Chỉ dùng let khi cần gán lại
  • [ ] Không bị variable shadowing (trừ khi có chủ đích)
  • [ ] Sử dụng block scope đúng cách

✅ Functions

javascript
// ❌ KHÔNG TỐT
const user = {
  name: 'John',
  greet: () => {
    // Arrow function làm mất this
    console.log(this.name);
  },
};

// ✅ TỐT
const user = {
  name: 'John',
  greet() {
    // Shorthand method
    console.log(this.name);
  },
};

// ❌ KHÔNG TỐT
const double = (x) => {
  return x * 2;
}; // Dùng braces không cần thiết

// ✅ TỐT
const double = (x) => x * 2; // Gọn gàng

Checklist:

  • [ ] Arrow functions dùng cho callbacks
  • [ ] Regular functions dùng cho methods
  • [ ] Dùng implicit return khi có thể
  • [ ] Không dùng parentheses không cần thiết cho single parameter
  • [ ] Không dùng braces không cần thiết cho single expression

✅ Destructuring

javascript
// ❌ KHÔNG TỐT
function displayUser(user) {
  const name = user.name;
  const email = user.email;
  const age = user.age;
}

// ✅ TỐT
function displayUser({ name, email, age }) {
  // Truy cập trực tiếp properties
}

// ❌ KHÔNG TỐT
const first = arr[0];
const second = arr[1];

// ✅ TỐT
const [first, second] = arr;

Checklist:

  • [ ] Object destructuring dùng trong function parameters
  • [ ] Nested destructuring dùng hợp lý (không quá sâu)
  • [ ] Có default values khi cần
  • [ ] Rest operator dùng để gom các properties còn lại
  • [ ] Array destructuring dùng cho multiple return values

✅ Immutability

javascript
// ❌ KHÔNG TỐT
function addItem(cart, item) {
  cart.push(item); // Mutation!
  return cart;
}

// ✅ TỐT
function addItem(cart, item) {
  return [...cart, item]; // Tạo array mới
}

// ❌ KHÔNG TỐT
function updateUser(user, updates) {
  user.name = updates.name; // Mutation!
  return user;
}

// ✅ TỐT
function updateUser(user, updates) {
  return { ...user, ...updates }; // Tạo object mới
}

Checklist:

  • [ ] Không mutate array trực tiếp (push, splice, v.v.)
  • [ ] Không mutate object trực tiếp
  • [ ] Dùng spread operator để copy
  • [ ] Dùng array methods (map, filter, reduce) thay cho loops
  • [ ] Functions luôn return array/object mới

✅ Array Methods

javascript
// ❌ KHÔNG TỐT
const doubled = [];
for (let i = 0; i < numbers.length; i++) {
  doubled.push(numbers[i] * 2);
}

// ✅ TỐT
const doubled = numbers.map((n) => n * 2);

// ❌ KHÔNG TỐT
numbers.forEach((n) => {
  console.log(n); // Side effect không có transformation
});

// ✅ TỐT
// forEach phù hợp cho side effects
// Nhưng không nên return bất cứ giá trị gì từ forEach

// ❌ KHÔNG TỐT
const result = numbers
  .map((n) => n * 2)
  .map((n) => n + 1)
  .map((n) => n.toString());

// ✅ TỐT
const result = numbers.map((n) => (n * 2 + 1).toString());

Checklist:

  • [ ] map dùng cho transformations
  • [ ] filter dùng để giữ lại items
  • [ ] reduce dùng cho aggregations
  • [ ] Không dùng forEach khi map / filter phù hợp hơn
  • [ ] Gộp nhiều map khi có thể
  • [ ] Array methods được chain hợp lý

✅ Template Literals

javascript
// ❌ KHÔNG TỐT
const message = 'Hello ' + name + ', you are ' + age + ' years old';

// ✅ TỐT
const message = `Hello ${name}, you are ${age} years old`;

// ❌ KHÔNG TỐT
const html =
  '<div class="' +
  className +
  '">' +
  '<h1>' +
  title +
  '</h1>' +
  '<p>' +
  content +
  '</p>' +
  '</div>';

// ✅ TỐT
const html = `
  <div class="${className}">
    <h1>${title}</h1>
    <p>${content}</p>
  </div>
`;

Checklist:

  • [ ] Dùng template literals thay cho string concatenation
  • [ ] Multi-line strings dùng template literals
  • [ ] Tách complex expressions ra variables (để dễ đọc)
  • [ ] Không dùng template literals không cần thiết cho static strings

🎯 Ví dụ Review Hoàn Chỉnh

javascript
// ❌ BEFORE (nhiều issues)
var users = [];

function processUsers(data) {
  for (var i = 0; i < data.length; i++) {
    var user = data[i];
    if (user.active == true) {
      var processed = {
        id: user.id,
        name: user.first_name + ' ' + user.last_name,
        email: user.email,
      };
      users.push(processed);
    }
  }
  return users;
}

// ✅ AFTER (đã fix tất cả issues)
const processUsers = (data) => {
  return data
    .filter((user) => user.active === true) // So sánh tường minh
    .map(({ id, first_name, last_name, email }) => ({
      id,
      name: `${first_name} ${last_name}`,
      email,
    }));
};

// Even better: Tách riêng phần transformation
const transformUser = ({ id, first_name, last_name, email }) => ({
  id,
  name: `${first_name} ${last_name}`,
  email,
});

const processUsers = (data) =>
  data.filter((user) => user.active).map(transformUser);

Issues Đã Được Fix:

  1. varconst
  2. ✅ Manual loop → filter + map
  3. ✅ String concatenation → Template literal
  4. ✅ Loose equality → Strict equality
  5. ✅ Function expression → Arrow function
  6. ✅ Mutation → Pure function (không dùng external state)
  7. ✅ Manual property picking → Destructuring

IX. COMMON PITFALLS & SOLUTIONS (20 phút)

🚨 Pitfall 1: Const Confusion (Nhầm lẫn về const)

Problem (Vấn đề):

javascript
const user = { name: 'John' };
user = { name: 'Jane' }; // ❌ Error: Assignment to constant (Lỗi: Gán giá trị cho hằng số)

const colors = ['red', 'green'];
colors = ['blue']; // ❌ Error: Assignment to constant (Lỗi: Gán giá trị cho hằng số)

Solution (Giải pháp):

javascript
// ✅ const ngăn REASSIGNMENT (gán lại), không ngăn MUTATION (thay đổi bên trong)
const user = { name: 'John' };
user.name = 'Jane'; // ✅ OK - mutating object (thay đổi object)

const colors = ['red', 'green'];
colors.push('blue'); // ✅ OK - mutating array (thay đổi array)

// Nhưng để đảm bảo immutability, hãy tạo object/array mới:
const updatedUser = { ...user, name: 'Jane' };
const updatedColors = [...colors, 'blue'];

🚨 Pitfall 2: Arrow Function Returns (Return của arrow function)

Problem (Vấn đề):

javascript
// ❌ Returns undefined!
const getUser = () => {
  id: 1,
  name: 'John'
};

// JavaScript thinks it's a code block, not an object!
// (JavaScript hiểu đây là block code, không phải object)

Solution (Giải pháp):

javascript
// ✅ Bọc object trong dấu ngoặc ()
const getUser = () => ({
  id: 1,
  name: 'John',
});

// Hoặc dùng explicit return
const getUser = () => {
  return {
    id: 1,
    name: 'John',
  };
};

🚨 Pitfall 3: Destructuring Default Values (Giá trị mặc định khi destructuring)

Problem (Vấn đề):

javascript
const user = { name: 'John' };

// ❌ Age is undefined, not 25!
const { name, age = 25 } = user;
// age is undefined (not 25) because user.age exists and is undefined
// (age là undefined, không phải 25 vì user.age tồn tại nhưng có giá trị undefined)

Solution (Giải pháp):

javascript
// ✅ Default values chỉ hoạt động khi property KHÔNG tồn tại
const user = { name: 'John' }; // Không có age
const { name, age = 25 } = user; // age là 25 ✓

// Với giá trị undefined, dùng nullish coalescing:
const user = { name: 'John', age: undefined };
const age = user.age ?? 25; // 25

🚨 Pitfall 4: Spread Operator is Shallow (Spread operator là shallow copy)

Problem (Vấn đề):

javascript
const user = {
  name: 'John',
  address: {
    city: 'New York',
  },
};

const clone = { ...user };
clone.address.city = 'Boston'; // ❌ Mutates original! (Thay đổi object gốc!)

console.log(user.address.city); // 'Boston' (unexpected! - không mong muốn)

Solution (Giải pháp):

javascript
// ✅ Spread thủ công cho nested object
const clone = {
  ...user,
  address: { ...user.address },
};

// ✅ Hoặc dùng deep clone utility
const deepClone = JSON.parse(JSON.stringify(user));
// (Đơn giản nhưng có hạn chế: mất function, Date, v.v.)

// ✅ Hoặc dùng thư viện (production)
import { cloneDeep } from 'lodash';
const clone = cloneDeep(user);

🚨 Pitfall 5: Map Returns Undefined (map trả về undefined)

Problem (Vấn đề):

javascript
const numbers = [1, 2, 3];

// ❌ Returns [undefined, undefined, undefined]
const doubled = numbers.map((n) => {
  n * 2; // Không có return!
});

Solution (Giải pháp):

javascript
// ✅ Implicit return (không dùng {})
const doubled = numbers.map((n) => n * 2);

// ✅ Explicit return (có {})
const doubled = numbers.map((n) => {
  return n * 2;
});

🚨 Pitfall 6: Reduce Without Initial Value (reduce không có initial value)

Problem (Vấn đề):

javascript
const numbers = [1, 2, 3, 4];

// ❌ Chạy được với numbers nhưng là pattern nguy hiểm
const sum = numbers.reduce((acc, n) => acc + n);

// ❌ Lỗi với array rỗng!
const empty = [];
const sum = empty.reduce((acc, n) => acc + n); // TypeError! (Lỗi kiểu)

Solution (Giải pháp):

javascript
// ✅ Luôn cung cấp initial value
const sum = numbers.reduce((acc, n) => acc + n, 0);

// ✅ An toàn với array rỗng
const sum = [].reduce((acc, n) => acc + n, 0); // 0

// ✅ Dùng initial value đúng với kiểu dữ liệu
const grouped = items.reduce((acc, item) => {
  // ...
}, {}); // Object initial value

const flattened = nested.reduce((acc, arr) => {
  // ...
}, []); // Array initial value

X. PERFORMANCE TIPS (15 phút)

⚡ Tip 1: Avoid Unnecessary Array Iterations (Tránh lặp array không cần thiết)

javascript
// ❌ BAD: Multiple iterations (Nhiều lần lặp)
const result = data
  .filter((item) => item.active)
  .map((item) => item.value)
  .filter((value) => value > 10);

// ✅ GOOD: Single iteration with reduce (Một lần lặp với reduce)
const result = data.reduce((acc, item) => {
  if (item.active && item.value > 10) {
    acc.push(item.value);
  }
  return acc;
}, []);

// 🎯 BEST: Only optimize if profiling shows it's a bottleneck!
// (Chỉ tối ưu khi profiling cho thấy đây là điểm nghẽn hiệu năng)
// Readability usually wins over micro-optimizations
// (Tính dễ đọc thường quan trọng hơn micro-optimization)

⚡ Tip 2: Memoize Expensive Operations (Memoize các phép tính tốn tài nguyên)

javascript
// ❌ BAD: Recalculates every time (Tính toán lại mỗi lần)
function processData(data) {
  const sorted = data.sort((a, b) => b.value - a.value);
  const top10 = sorted.slice(0, 10);
  return top10;
}

// ✅ GOOD: Memoize result (Ghi nhớ kết quả)
const memoize = (fn) => {
  const cache = new Map();
  return (...args) => {
    const key = JSON.stringify(args);
    if (cache.has(key)) return cache.get(key);
    const result = fn(...args);
    cache.set(key, result);
    return result;
  };
};

const processData = memoize((data) => {
  const sorted = [...data].sort((a, b) => b.value - a.value);
  return sorted.slice(0, 10);
});

⚡ Tip 3: Use Object Lookup Instead of Array.find

(Sử dụng tra cứu object thay vì Array.find)

javascript
// ❌ SLOW: O(n) for each lookup (Chậm: O(n) cho mỗi lần tìm)
const users = [
  /* large array */
];
const user1 = users.find((u) => u.id === 1);
const user2 = users.find((u) => u.id === 2);

// ✅ FAST: O(1) for each lookup (Nhanh: O(1) cho mỗi lần tìm)
const usersById = users.reduce((acc, user) => {
  acc[user.id] = user;
  return acc;
}, {});

const user1 = usersById[1];
const user2 = usersById[2];

XI. TỔNG KẾT NGÀY HỌC & BƯỚC TIẾP THEO (10 phút)

🎯 Hôm nay đã học:

Core Concepts (Khái niệm cốt lõi):

  • let/const và block scope
  • ✅ Arrow functions và this binding
  • ✅ Template literals
  • ✅ Destructuring (objects & arrays)
  • ✅ Spread/Rest operators
  • ✅ Array methods (map, filter, reduce)

Key Takeaways (Điểm quan trọng):

  1. Luôn dùng const mặc định, chỉ dùng let khi cần reassignment
  2. Arrow functions dùng cho callbacks, regular functions dùng cho methods
  3. Destructuring giúp code sạch và dễ đọc hơn
  4. Spread operator dùng cho immutable updates (rất quan trọng với React)
  5. Array methods thay thế loops để code dễ đọc hơn

📝 Self-Assessment Questions:

Tự trả lời các câu hỏi sau (không cần code):

  1. Khi nào dùng let vs const?
  2. Tại sao arrow function không phù hợp làm object method?
  3. Spread operator shallow hay deep copy?
  4. Khi nào dùng map vs forEach vs reduce?
  5. Làm sao tránh mutation khi update object trong array?
💡 Answers
  1. let vs const: Dùng const cho values không reassign. Dùng let chỉ khi cần reassignment (loops, counters). Const không prevent mutation (object/array methods vẫn hoạt động).

  2. Arrow functions as methods: Arrow functions có lexical this (inherit từ surrounding scope), không phải dynamic this. Object methods cần dynamic this để reference object itself.

  3. Spread shallow/deep: Spread operator là shallow copy. Chỉ copy level đầu tiên. Nested objects/arrays vẫn là references. Cần spread manually hoặc dùng deep clone utility.

  4. map vs forEach vs reduce:

    • map: Transform mỗi item, return new array
    • forEach: Side effects only, return undefined
    • reduce: Aggregate thành single value (hoặc build complex result)
  5. Avoid mutation: Dùng map để create new array với updated objects: items.map((item, i) => i === index ? { ...item, ...updates } : item)


🔗 Ngày mai sẽ học:

ES6+ Advanced (Day 2):

  • Promises & async/await - Asynchronous operations - Bất đồng bộ
  • Modules - Tổ chức code với import/export
  • Optional chaining - Truy cập thuộc tính an toàn với chaining
  • Nullish coalescing - Xử lý giá trị mặc định với (??)
  • Advanced array methods - flatMap, find, some, every

Preparation:

  • [ ] Review hôm nay's exercises
  • [ ] Complete any unfinished challenges
  • [ ] Read about Promises (optional)
  • [ ] Install Node.js (if not yet installed)

📚 Additional Resources:

Official Docs:

Practice:

Deep Dives:


XII. HOMEWORK & PRACTICE (Optional)

🏠 Tonight's Practice:

1. Refactor Old Code (30 phút)

Tìm old JavaScript code của bạn (hoặc code online) và refactor sử dụng ES6+:

  • Replace var với let/const
  • Convert functions sang arrow functions
  • Add destructuring
  • Replace loops với array methods

2. Build Mini Project (60 phút)

Todo List Manager (Pure JavaScript, NO React yet):

javascript
// Requirements:
// - Add/remove/update todos
// - Filter by status (all/active/completed)
// - Sort by date/priority
// - Calculate statistics
// - All using ES6+ features

3. Code Challenge (45 phút)

Code các hàm tiện ích sau:

  • flatten(array, depth) - Flatten nested arrays. Làm phẳng mảng lồng nhau theo độ sâu (depth)
  • difference(arr1, arr2) - Items in arr1 not in arr2. Lấy các phần tử có trong arr1 nhưng không có trong arr2
  • intersection(arr1, arr2) - Items in both arrays. Lấy các phần tử xuất hiện ở cả hai mảng
  • union(arr1, arr2) - All unique items. Gộp hai mảng thành một mảng duy nhất không trùng lặp
  • partition(array, predicate) - Split by condition. Tách mảng dựa trên điều kiện (predicate)

🎉 CONGRATULATIONS!

Bạn đã hoàn thành Ngày 1: ES6+ Essentials cho React!

📊 Progress Tracker:

PHASE 1: React Fundamentals (Week 1-4)
═══════════════════════════════════════
Week 1: Modern JavaScript (Days 1-5)
├─ [█████████░] 20% Complete
│  └─ Day 1: ES6+ Essentials ✅
│  └─ Day 2: ES6+ Advanced
│  └─ Day 3: React Basics & JSX
│  └─ Day 4: Components & Props
│  └─ Day 5: Events & Conditional Rendering

You're on track! 🚀


💬 Final Tips:

  1. Practice Daily - Code mỗi ngày, dù chỉ 30 phút
  2. Build Real Things - Apply concepts vào projects thực tế
  3. Debug Actively - Mỗi bug là cơ hội học
  4. Ask Questions - Không có câu hỏi ngu, chỉ có người không hỏi
  5. Review Often - Quay lại material này khi cần

❓ Questions Before Next Lesson?

Review checklist:

  • [ ] Hiểu khác biệt let/const/var
  • [ ] Thoải mái với arrow functions
  • [ ] Biết khi nào dùng destructuring
  • [ ] Hiểu spread operator và immutability
  • [ ] Thành thạo map/filter/reduce

Nếu còn unclear ở bất kỳ phần nào, hãy review lại section đó trước khi chuyển sang Day 2!


See you tomorrow for Day 2: ES6+ Advanced! 👋

Personal tech knowledge base