📚 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:
let/constvà block scope- Arrow functions và
thisbinding - Template literals
- Destructuring (objects & arrays)
- Spread/Rest operators
- 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 ReactIII. 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:
// ❌ 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:
// ✅ ĐÚ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):
// ❌ 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):
// ✅ 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):
// ❌ 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):
// ✅ 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):
// ❌ 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):
// ✅ 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):
// ✅ 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ế):
// 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):
// ❌ 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):
// ✅ 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):
// ✅ 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:
// 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):
| Method | Returns (Giá trị trả về) | Mảng gốc | Use Case (Trường hợp dùng) |
|---|---|---|---|
map | New array (same length) | Không thay đổi | Transform each item (biến đổi từng phần tử) |
filter | New array (≤ length) | Không thay đổi | Keep matching items (giữ phần tử phù hợp) |
reduce | Single value | Không thay đổi | Combine/aggregate (gộp / tổng hợp) |
find | Single item or undefined | Không thay đổi | First match (khớp đầu tiên) |
findIndex | Index or -1 | Không thay đổi | Position of match (vị trí phần tử khớp) |
some | Boolean | Không thay đổi | Any match? (có phần tử nào khớp?) |
every | Boolean | Không thay đổi | All match? (tất cả đều khớp?) |
forEach | undefined | Không thay đổi | Side 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):
// ❌ 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ử):
// ✅ 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
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ả |
|---|---|
element | Phần tử hiện tại đang được xử lý |
index | Chỉ số của phần tử hiện tại |
array | Mả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
// ✅ 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):
// ✅ 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):
// ✅ 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):
// ✅ 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 (⭐)
// 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 (⭐⭐)
// 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 (⭐⭐⭐)
// 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.
// 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:
- Replace
varvớilethoặcconstappropriately - Fix scope issues
- Add comments giải thích choices
Expected Output: Code chạy giống nhau nhưng safer.
💡 Solution
// ✅ 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:
constfor values that never change (dùng const cho giá trị không bao giờ thay đổi)letfor 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.
// 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:
- Convert all functions to arrow functions (chuyển tất cả functions sang arrow functions)
- Use destructuring trong parameters (dùng destructuring trong parameters)
- Use template literals (dùng template literals)
- Replace loops với array methods (thay vòng lặp bằng array methods)
- Make code concise và readable (làm code ngắn gọn và dễ đọc)
💡 Solution
// ✅ 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.
// 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+:
addItem(cart, productId, quantity)- Add/update itemremoveItem(cart, productId)- Remove itemupdateQuantity(cart, productId, quantity)- Update quantitycalculateSubtotal(cart, products)- Total before taxcalculateTax(subtotal, taxRate)- Calculate taxcalculateTotal(cart, products, taxRate)- Final totalgetCartSummary(cart, products)- Detailed summary.Ouput:
{ items, itemCount, subtotal, tax, total }applyDiscount(total, discountCode)- Apply discount codes.Ouput:
{ total, discount, message }checkStock(cart, products)- Verify availability (kiểm tra tồn kho).Ouput:
{ allAvailable, outOfStock:[] }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
// ✅ 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.
// 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:
getTotalRevenue(data)- Total from all completed ordersgetRevenueByDate(data)- Revenue grouped by dategetRevenueByCategory(data)- Revenue by product categorygetTopProducts(data, limit)- Best selling productsgetCustomerStats(data)- Customer purchase statisticsgetAverageOrderValue(data)- AOV for completed orders (Giá trị đơn hàng trung bình (AOV) của các đơn đã hoàn thành)getProductPerformance(data)- Detailed product metrics (Các chỉ số chi tiết về hiệu suất sản phẩm)getDailySummary(data)- Complete daily breakdown (Báo cáo tổng hợp theo ngày)getCustomerSegments(data)- Segment customers by spend (Phân nhóm khách hàng theo mức chi tiêu)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
// ✅ 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:
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:
chunk(array, size)- Chia array thành các chunksunique(array)- Loại bỏ duplicatesgroupBy(array, key)- Group theo propertysortBy(array, key)- Sort theo propertypluck(array, key)- Extract giá trị của property
Object Utilities:
pick(object, keys)- Pick các keys cụ thểomit(object, keys)- Omit các keys cụ thểmerge(objects...)- Deep merge objectsflatten(object)- Flatten nested objectinvert(object)- Swap keys và values
Function Utilities:
pipe(...fns)- Compose functions theo thứ tự left-to-rightcompose(...fns)- Compose functions theo thứ tự right-to-leftdebounce(fn, delay)- Debounce functionthrottle(fn, delay)- Throttle functionmemoize(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
// ✅ 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:
// 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
// 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
// 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
// Đ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
// 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
// Đ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
// 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, hoistedlet: Block-scoped, cannot be redeclared, hoisted but not initializedconst: Block-scoped, cannot be reassigned (but objects/arrays can be mutated)
// 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
thisbinding (inherit from surrounding scope) - Cannot be used as constructors
- No
argumentsobject
Don't use arrow functions for:
- Object methods (loses
this) - Event handlers that need
this - Functions that need
arguments
// ✅ 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)
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
// 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:
- Takes accumulator (initial value) and current item
- Executes reducer function
- Returns new accumulator value
- Passes to next iteration
- 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
// 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):
- Manual assignment (fastest)
Object.assign- Spread operator (slowest)
// 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:
| Method | Performance | Readability | Maintainability | Use 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:
// 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:
// 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:
// 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:
// 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:
// 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
// ❌ 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ạiChecklist:
- [ ] Không sử dụng
var - [ ] Dùng
constcho các giá trị không thay đổi - [ ] Chỉ dùng
letkhi 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
// ❌ 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àngChecklist:
- [ ] 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
// ❌ 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
// ❌ 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
// ❌ 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:
- [ ]
mapdùng cho transformations - [ ]
filterdùng để giữ lại items - [ ]
reducedùng cho aggregations - [ ] Không dùng
forEachkhimap/filterphù hợp hơn - [ ] Gộp nhiều
mapkhi có thể - [ ] Array methods được chain hợp lý
✅ Template Literals
// ❌ 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
// ❌ 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:
- ✅
var→const - ✅ Manual loop →
filter+map - ✅ String concatenation → Template literal
- ✅ Loose equality → Strict equality
- ✅ Function expression → Arrow function
- ✅ Mutation → Pure function (không dùng external state)
- ✅ Manual property picking → Destructuring
IX. COMMON PITFALLS & SOLUTIONS (20 phút)
🚨 Pitfall 1: Const Confusion (Nhầm lẫn về const)
Problem (Vấn đề):
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):
// ✅ 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 đề):
// ❌ 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):
// ✅ 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 đề):
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):
// ✅ 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 đề):
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):
// ✅ 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 đề):
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):
// ✅ 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 đề):
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):
// ✅ 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 valueX. PERFORMANCE TIPS (15 phút)
⚡ Tip 1: Avoid Unnecessary Array Iterations (Tránh lặp array không cần thiết)
// ❌ 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)
// ❌ 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)
// ❌ 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/constvà block scope - ✅ Arrow functions và
thisbinding - ✅ Template literals
- ✅ Destructuring (objects & arrays)
- ✅ Spread/Rest operators
- ✅ Array methods (
map,filter,reduce)
Key Takeaways (Điểm quan trọng):
- Luôn dùng
constmặc định, chỉ dùngletkhi cần reassignment - Arrow functions dùng cho callbacks, regular functions dùng cho methods
- Destructuring giúp code sạch và dễ đọc hơn
- Spread operator dùng cho immutable updates (rất quan trọng với React)
- 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):
- Khi nào dùng
letvsconst? - Tại sao arrow function không phù hợp làm object method?
- Spread operator shallow hay deep copy?
- Khi nào dùng
mapvsforEachvsreduce? - Làm sao tránh mutation khi update object trong array?
💡 Answers
let vs const: Dùng
constcho values không reassign. Dùngletchỉ khi cần reassignment (loops, counters). Const không prevent mutation (object/array methods vẫn hoạt động).Arrow functions as methods: Arrow functions có lexical
this(inherit từ surrounding scope), không phải dynamicthis. Object methods cần dynamicthisđể reference object itself.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.
map vs forEach vs reduce:
map: Transform mỗi item, return new arrayforEach: Side effects only, return undefinedreduce: Aggregate thành single value (hoặc build complex result)
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:
- JavaScript30 - 30 coding challenges
- Exercism JavaScript Track - Practice with mentoring
Deep Dives:
- JavaScript Info - Comprehensive tutorial
- You Don't Know JS - Book series
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
varvớilet/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):
// Requirements:
// - Add/remove/update todos
// - Filter by status (all/active/completed)
// - Sort by date/priority
// - Calculate statistics
// - All using ES6+ features3. 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 arr2intersection(arr1, arr2)- Items in both arrays. Lấy các phần tử xuất hiện ở cả hai mảngunion(arr1, arr2)- All unique items. Gộp hai mảng thành một mảng duy nhất không trùng lặppartition(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 RenderingYou're on track! 🚀
💬 Final Tips:
- Practice Daily - Code mỗi ngày, dù chỉ 30 phút
- Build Real Things - Apply concepts vào projects thực tế
- Debug Actively - Mỗi bug là cơ hội học
- Ask Questions - Không có câu hỏi ngu, chỉ có người không hỏi
- 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! 👋