- Tạo array đúng cách, hiểu vấn đề sparse array với
new Array(n). - Phân biệt rạch ròi method mutate vs non-mutate; biết các bản ES2023 (
toSorted,toReversed). - Master bộ ba
map/filter/reduce. Đặc biệt làreduce— pattern gom nhóm, đếm, build object. - Biết khi nào chọn
forEach,for/of,for— và vì saofor/ofnên là default. - Hiểu spread/rest cùng cú pháp
...nhưng khác ngữ cảnh. - Destructuring nâng cao: nested, rename, default, rest.
- Iteration protocol: tự build iterable, dùng
function*để tạo sequence lười. - Phân biệt Set/Map/WeakSet/WeakMap — chọn đúng cho từng tình huống.
1. Tạo array — và cái bẫy sparse
JS có nhiều cách tạo array, nhưng không phải cái nào cũng tương đương.
// 1. Literal — cách dùng 99% trường hợp
const a = [1, 2, 3];
// 2. new Array(n) — tạo array length n nhưng RỖNG (sparse) 🔥
const b = new Array(3);
console.log(b); // [ <3 empty items> ]
console.log(b.length); // 3
console.log(b[0]); // undefined — nhưng KHÔNG phải undefined thật
// 3. Array.of — fix bug new Array(n)
Array.of(3); // [3] — luôn coi arg như phần tử
Array.of(1, 2, 3); // [1, 2, 3]
// 4. Array.from — convert iterable hoặc array-like sang array thật
Array.from('abc'); // ['a', 'b', 'c']
Array.from({ length: 3 }); // [undefined, undefined, undefined]
Array.from({ length: 3 }, (_, i) => i); // [0, 1, 2]
// 5. Spread cho iterable → array
[...'abc']; // ['a', 'b', 'c']
[...new Set([1, 2, 2])]; // [1, 2] ← dedupe trick
new Array(3) tạo sparse array: slot rỗng (empty), khác với [undefined, undefined, undefined]. Sparse slot bị nhiều method bỏ qua:
const sparse = new Array(3);
const dense = [undefined, undefined, undefined];
sparse.map((x) => 1); // [ <3 empty> ] ← KHÔNG chạy callback!
dense.map((x) => 1); // [1, 1, 1]
sparse.forEach((x) => console.log(x)); // không in gì
dense.forEach((x) => console.log(x)); // in 3 lần undefined
Quy tắc: tránh new Array(n). Cần khởi tạo array length n thì dùng
Array.from({ length: n }, (_, i) => ...).
length là property writable, không phải read-only:
const arr = [1, 2, 3, 4, 5];
arr.length = 2; // truncate
console.log(arr); // [1, 2]
arr.length = 5; // extend với empty slots — TẠO SPARSE!
console.log(arr); // [1, 2, <3 empty>]
2. Mutating vs non-mutating — bảng cheat
Đây là kiến thức bắt buộc. Trộn lẫn 2 nhóm này gây bug khó nhất trong JS (mutate state vô tình).
| Mutating (sửa mảng gốc) | Non-mutating (trả mảng mới) |
|---|---|
push, pop | concat |
shift, unshift | slice |
splice | map, filter, flat, flatMap |
sort | toSorted (ES2023) |
reverse | toReversed (ES2023) |
fill, copyWithin | toSpliced (ES2023) |
| — | with(i, v) (ES2023) thay arr[i] = v immutable |
const a = [3, 1, 2];
// ❌ mutate: thay đổi a
a.sort();
console.log(a); // [1, 2, 3] ← a đã bị đổi!
// ✅ immutable: a giữ nguyên
const b = [3, 1, 2];
const sorted = b.toSorted();
console.log(b); // [3, 1, 2]
console.log(sorted); // [1, 2, 3]
// with(i, v) — replace 1 phần tử, trả mảng mới
const c = ['a', 'b', 'c'];
const d = c.with(1, 'B'); // ['a', 'B', 'c']
console.log(c); // ['a', 'b', 'c'] — c không đổi
ES2023 đặt tên bằng tiền tố to-: toSorted, toReversed, toSpliced.
Tiền tố to = "trả về phiên bản mới". Trong React/Redux/Vuex, mọi update state nên non-mutate.
3. Bộ ba map / filter / reduce
3 method functional cốt lõi. Học thuộc signature, gotcha, và pattern.
3.1. map — biến đổi từng phần tử
// arr.map(callback(element, index, array)) → array mới CÙNG length
const nums = [1, 2, 3, 4];
nums.map((x) => x * 2); // [2, 4, 6, 8]
nums.map((x, i) => `${i}: ${x}`); // ['0: 1', '1: 2', ...]
// Map object → property
const users = [{ name: 'An', age: 20 }, { name: 'Bình', age: 25 }];
users.map((u) => u.name); // ['An', 'Bình']
3.2. filter — giữ phần tử match predicate
// arr.filter(predicate(element, index, array)) → array mới (length ≤ gốc)
[1, 2, 3, 4, 5].filter((x) => x % 2 === 0); // [2, 4]
users.filter((u) => u.age >= 21); // [{ name: 'Bình', age: 25 }]
// Boolean shortcut — bỏ falsy
[1, 0, 'a', '', null, 5].filter(Boolean); // [1, 'a', 5]
3.3. reduce — gộp về 1 giá trị (Swiss-army knife)
// arr.reduce(callback(accumulator, element, index, array), initialValue)
// Sum
[1, 2, 3, 4].reduce((acc, x) => acc + x, 0); // 10
// Max
[3, 7, 1, 9, 4].reduce((m, x) => x > m ? x : m, -Infinity); // 9
4 pattern kinh điển dùng reduce:
const orders = [
{ id: 1, total: 120 },
{ id: 2, total: 80 },
{ id: 3, total: 200 },
];
const revenue = orders.reduce((acc, o) => acc + o.total, 0);
// 400
const people = [
{ name: 'An', city: 'HN' },
{ name: 'Bình', city: 'HCM' },
{ name: 'Cường', city: 'HN' },
];
const byCity = people.reduce((acc, p) => {
(acc[p.city] ??= []).push(p);
return acc;
}, {});
// { HN: [{...An}, {...Cường}], HCM: [{...Bình}] }
// ES2024 có Object.groupBy built-in:
Object.groupBy(people, (p) => p.city); // same result
const votes = ['A', 'B', 'A', 'C', 'A', 'B'];
const tally = votes.reduce((acc, v) => {
acc[v] = (acc[v] ?? 0) + 1;
return acc;
}, {});
// { A: 3, B: 2, C: 1 }
const pairs = [['a', 1], ['b', 2], ['c', 3]];
const obj = pairs.reduce((acc, [k, v]) => {
acc[k] = v;
return acc;
}, {});
// { a: 1, b: 2, c: 3 }
// Built-in tương đương:
Object.fromEntries(pairs); // { a: 1, b: 2, c: 3 }
Nếu không truyền initialValue, reduce dùng phần tử đầu làm acc và bắt đầu từ index 1.
Trên array rỗng: TypeError: Reduce of empty array with no initial value.
[].reduce((a, b) => a + b); // ❌ TypeError
[].reduce((a, b) => a + b, 0); // ✅ 0
Luôn truyền initial value — vừa an toàn, vừa rõ type của accumulator.
4. forEach vs for/of vs for
3 cách duyệt array, mỗi cái có ưu/nhược.
| — | forEach | for/of | cổ điển for |
|---|---|---|---|
break được không | ❌ (chỉ return bên trong callback) | ✅ | ✅ |
await tuần tự | ❌ (callback chạy đồng thời) | ✅ | ✅ |
| Truy cập index | (v, i) => | arr.entries() | biến i |
| Skip sparse slot | Bỏ qua | Trả undefined | Trả undefined |
| Iterable nói chung | Chỉ Array | ✅ mọi iterable | Chỉ index được |
| Performance | Chậm hơn chút | Nhanh | Nhanh nhất (vi mô) |
// ❌ forEach không break được
[1, 2, 3, 4].forEach((x) => {
if (x === 3) return; // chỉ skip phần tử này, không stop loop
console.log(x);
});
// ✅ for/of break được
for (const x of [1, 2, 3, 4]) {
if (x === 3) break;
console.log(x);
}
// ❌ forEach KHÔNG await tuần tự
async function bad() {
[url1, url2, url3].forEach(async (u) => {
await fetch(u); // 3 request chạy đồng thời, "bad" return trước khi xong
});
}
// ✅ for/of await tuần tự
async function good() {
for (const u of [url1, url2, url3]) {
await fetch(u); // chạy lần lượt
}
}
Dùng for/of khi cần side-effect / loop bình thường. Dùng
map/filter/reduce khi muốn transform / lọc / gộp. Tránh forEach —
không có ưu điểm gì mà mất khả năng break, async.
const items = ['a', 'b', 'c'];
for (const [i, v] of items.entries()) {
console.log(i, v); // 0 'a', 1 'b', 2 'c'
}
5. find / some / every / includes — early return
Khác map/filter ở chỗ dừng sớm khi tìm thấy / không match.
| Method | Trả về | Dừng khi | Mảng rỗng |
|---|---|---|---|
find(pred) | Phần tử đầu match, hoặc undefined | Match đầu tiên | undefined |
findIndex(pred) | Index của match đầu, hoặc -1 | Match đầu tiên | -1 |
findLast / findLastIndex (ES2023) | Như trên, từ phải qua | Match đầu (từ cuối) | — |
some(pred) | boolean — có ít nhất 1 match | Match đầu tiên | false |
every(pred) | boolean — TẤT CẢ match | Không-match đầu tiên | true 🔥 |
includes(v) | boolean — có v không | Match đầu (so sánh SameValueZero) | false |
const nums = [1, 2, 3, 4, 5];
nums.find((x) => x > 3); // 4
nums.findIndex((x) => x > 3); // 3
nums.findLast((x) => x < 4); // 3
nums.some((x) => x > 10); // false
nums.every((x) => x > 0); // true
nums.includes(3); // true
nums.includes(NaN); // (nếu array có NaN: true, vì SameValueZero)
[NaN].indexOf(NaN); // -1 🔥 (indexOf dùng ===)
every trên array rỗng = true"Mọi phần tử đều match" trên 0 phần tử thì vacuously true (logic toán học). Cẩn thận khi check "tất cả user đã verified" mà list rỗng — bạn vẫn được "yes".
6. flat / flatMap
flat(depth) làm phẳng nested array. flatMap = map().flat(1) nhưng hiệu quả hơn (1 lần duyệt).
[1, [2, [3, [4]]]].flat(); // [1, 2, [3, [4]]] (depth=1)
[1, [2, [3, [4]]]].flat(2); // [1, 2, 3, [4]]
[1, [2, [3, [4]]]].flat(Infinity); // [1, 2, 3, 4]
// flatMap — phổ biến: 1 element → nhiều element
const sentences = ['I love JS', 'You too'];
sentences.flatMap((s) => s.split(' '));
// ['I', 'love', 'JS', 'You', 'too']
// Filter + map gộp 1 lần:
[1, 2, 3, 4].flatMap((x) => x % 2 ? [x * 10] : []);
// [10, 30] ← chỉ giữ odd, nhân 10
7. sort gotcha — default là so sánh STRING
Đây là bug kinh điển bắt mọi JS developer ít nhất 1 lần.
[10, 1, 2, 25, 3].sort();
// [1, 10, 2, 25, 3] 🔥 SAI! sort by string
// Vì sao? Default sort convert mỗi phần tử sang string, so sánh code point.
// '10' < '2' khi so chuỗi (ký tự đầu '1' < '2').
// Cách đúng — truyền comparator:
[10, 1, 2, 25, 3].sort((a, b) => a - b);
// [1, 2, 3, 10, 25] ✅
// Comparator rule:
// trả < 0 → a đứng trước b
// trả > 0 → b đứng trước a
// trả = 0 → giữ nguyên thứ tự tương đối (stable từ 2019)
// Sort theo property:
users.sort((a, b) => a.age - b.age); // asc
users.sort((a, b) => b.age - a.age); // desc
// Sort string đúng locale (tiếng Việt có dấu):
['Đào', 'An', 'Bình'].sort((a, b) => a.localeCompare(b, 'vi'));
sort mutate — dùng toSorted nếu không muốn đổi gốcconst a = [3, 1, 2];
const b = a.toSorted((x, y) => x - y);
// a = [3, 1, 2] (giữ nguyên)
// b = [1, 2, 3]
8. Spread (...arr) và Rest (...args) — cùng cú pháp khác ngữ cảnh
Cùng dùng dấu ... nhưng:
- Spread: "trải" iterable ra — dùng ở function call, array literal, object literal.
- Rest: "gom" arg/element vào array — dùng ở function param, destructuring.
// Spread trong function call: array → individual args
Math.max(...[3, 1, 4, 1, 5]); // 5
// Spread trong array literal: clone / merge
const a = [1, 2];
const b = [3, 4];
const merged = [...a, ...b]; // [1, 2, 3, 4]
const cloned = [...a]; // shallow clone
// Spread trong object literal (ES2018):
const o1 = { a: 1, b: 2 };
const o2 = { ...o1, c: 3 }; // { a: 1, b: 2, c: 3 }
const o3 = { ...o1, b: 99 }; // { a: 1, b: 99 } ← override
// Rest trong function param: gom args còn lại vào array
function sum(...nums) {
return nums.reduce((a, b) => a + b, 0);
}
sum(1, 2, 3, 4); // 10
// Rest sau named param
function log(level, ...messages) {
console.log(`[${level}]`, ...messages);
}
log('INFO', 'a', 'b', 'c'); // [INFO] a b c
// Rest trong destructuring
const [first, ...others] = [1, 2, 3, 4];
// first = 1, others = [2, 3, 4]
const { a, ...restObj } = { a: 1, b: 2, c: 3 };
// a = 1, restObj = { b: 2, c: 3 }
=?
Nếu ... đứng bên trái (param, destructuring) → rest: gom vào.
Nếu ... đứng bên phải hoặc trong call/literal → spread: trải ra.
9. Destructuring nâng cao
Đã gặp ở chương 3-4. Đây là full reference.
const [a, b, c] = [1, 2, 3];
// a=1, b=2, c=3
// Skip phần tử
const [, , third] = [1, 2, 3]; // third=3
// Default value khi undefined
const [x = 10, y = 20] = [1]; // x=1, y=20
// Rest
const [head, ...tail] = [1, 2, 3, 4];
// head=1, tail=[2,3,4]
// Swap không cần biến temp
let p = 1, q = 2;
[p, q] = [q, p]; // p=2, q=1
const user = { name: 'An', age: 20, city: 'HN' };
// Cơ bản
const { name, age } = user;
// Rename — y trong object → newName trong scope
const { name: userName } = user; // userName = 'An'
// Default
const { phone = 'N/A' } = user; // phone = 'N/A'
// Rename + default
const { phone: tel = 'N/A' } = user;
// Nested
const obj = { a: { b: { c: 42 } } };
const { a: { b: { c } } } = obj; // c = 42
// Destructure trong param + default
function greet({ name = 'guest', lang = 'vi' } = {}) {
console.log(name, lang);
}
greet(); // 'guest' 'vi'
greet({ name: 'An' }); // 'An' 'vi'
greet({ name: 'An', lang: 'en' });
function parseUrl(url) {
const u = new URL(url);
return { host: u.host, path: u.pathname, query: u.search };
}
const { host, path } = parseUrl('https://example.com/x?y=1');
// host = 'example.com', path = '/x'
10. Iteration Protocol — vì sao for/of hoạt động
Một object là iterable nếu có method [Symbol.iterator](), method này trả về một
iterator. Iterator là object có method next() trả về
{ value, done }.
const arr = ['a', 'b', 'c'];
const it = arr[Symbol.iterator]();
it.next(); // { value: 'a', done: false }
it.next(); // { value: 'b', done: false }
it.next(); // { value: 'c', done: false }
it.next(); // { value: undefined, done: true }
Built-in iterables: Array, String, Map, Set, NodeList, arguments, TypedArray. Plain object thì không.
const range = {
from: 1,
to: 5,
[Symbol.iterator]() {
let current = this.from;
const last = this.to;
return {
next() {
return current <= last
? { value: current++, done: false }
: { value: undefined, done: true };
}
};
}
};
for (const n of range) console.log(n); // 1 2 3 4 5
[...range]; // [1, 2, 3, 4, 5]
Array.from(range); // [1, 2, 3, 4, 5]
for/of trên plain object → lỗiconst obj = { a: 1, b: 2 };
for (const x of obj) { } // TypeError: obj is not iterable
// Cách đúng:
for (const k of Object.keys(obj)) { }
for (const v of Object.values(obj)) { }
for (const [k, v] of Object.entries(obj)) { }
// Hoặc for/in (duyệt key, kể cả inherited - cẩn thận):
for (const k in obj) { }
11. Generator — function*
Generator là function dừng/tiếp được. Khi gọi, nó không chạy — trả về một generator object
(vừa là iterator vừa là iterable). Mỗi lần next() chạy đến yield tiếp theo rồi pause.
function* simple() {
yield 1;
yield 2;
yield 3;
}
const g = simple();
g.next(); // { value: 1, done: false }
g.next(); // { value: 2, done: false }
g.next(); // { value: 3, done: false }
g.next(); // { value: undefined, done: true }
// Generator object là iterable
[...simple()]; // [1, 2, 3]
for (const v of simple()) console.log(v);
function* range(from, to, step = 1) {
for (let i = from; i <= to; i += step) {
yield i;
}
}
[...range(1, 5)]; // [1, 2, 3, 4, 5]
[...range(0, 10, 2)]; // [0, 2, 4, 6, 8, 10]
function* naturals() {
let n = 1;
while (true) yield n++;
}
// Lấy 5 số đầu — KHÔNG vô hạn loop vì generator lazy
function* take(iter, n) {
for (const v of iter) {
if (n-- <= 0) return;
yield v;
}
}
[...take(naturals(), 5)]; // [1, 2, 3, 4, 5]
- Lazy: chỉ tính khi cần — viết được sequence vô hạn.
- Composable:
take,filter,mapbằng generator tạo pipeline streaming, không cần buộc array trung gian. - State machine: viết logic kiểu "đợi event, rồi đến event tiếp" tự nhiên hơn.
- Là nền tảng để hiểu async generator (chương 6).
12. Set / Map / WeakSet / WeakMap
4 collection ES6 ngoài array/object. Mỗi cái có chỗ dùng riêng.
| — | Set | Map | WeakSet | WeakMap |
|---|---|---|---|---|
| Lưu | Giá trị unique | Cặp key→value | Object unique | Cặp object→value |
| Key type | — | Bất kỳ (kể cả object) | — | Object only |
| Lookup | O(1) — has | O(1) — get/has | O(1) | O(1) |
| Iterable | ✅ insertion order | ✅ insertion order | ❌ | ❌ |
| size property | ✅ | ✅ | ❌ | ❌ |
| GC reference | Strong | Strong | Weak (cho phép GC) | Weak |
12.1. Set — collection unique
const s = new Set();
s.add(1); s.add(2); s.add(2); s.add(3);
s.size; // 3
s.has(2); // true
s.delete(2);
[...s]; // [1, 3]
// Dedupe array đơn giản:
const uniq = [...new Set([1, 2, 2, 3, 3, 3])]; // [1, 2, 3]
// So sánh performance: includes O(n) vs Set.has O(1)
const bigArr = Array.from({ length: 100000 }, (_, i) => i);
const bigSet = new Set(bigArr);
bigArr.includes(99999); // chậm — duyệt 100k phần tử
bigSet.has(99999); // nhanh — hash lookup
12.2. Map — key bất kỳ + insertion order
const m = new Map();
m.set('name', 'An');
m.set(42, 'số 42');
m.set(Symbol('id'), 1);
const obj = {};
m.set(obj, 'value cho object'); // Map dùng object làm key — khác plain Object!
m.get('name'); // 'An'
m.has(42); // true
m.size; // 4
// Iterate theo INSERTION ORDER
for (const [k, v] of m) { }
for (const k of m.keys()) { }
for (const v of m.values()) { }
// Khởi tạo từ array of pairs
const m2 = new Map([
['a', 1],
['b', 2],
]);
- Key type: Map cho phép key bất kỳ. Object chỉ string/Symbol.
- Order: Map đảm bảo insertion order. Object — chỉ đảm bảo từ ES2020, và integer-like key bị sort.
- Size: Map có
.size. Object phảiObject.keys(o).length. - Iterate: Map iterable trực tiếp. Object cần
Object.entries. - Performance: Map tối ưu cho thêm/xóa thường xuyên.
- Prototype pollution: Object có inherited key như
toString,__proto__. Map sạch. - Khi nào dùng Object: structured record (user, config). Khi nào dùng Map: dictionary động, cache, lookup table với key động.
12.3. WeakSet / WeakMap — không giữ GC reference
Strong reference (Map, Set, biến thường) chặn garbage collector. Weak reference cho phép object bị GC nếu không có chỗ nào khác giữ nó.
let user = { name: 'An' };
const wm = new WeakMap();
wm.set(user, 'metadata');
user = null; // không còn strong ref → entry trong WeakMap sẽ bị GC
// WeakMap KHÔNG iterable, không size, không keys() — vì entries có thể biến mất bất kỳ lúc nào
// Chỉ có: set / get / has / delete
Gắn metadata cho object mà không "giữ" object đó sống mãi. Pattern: private fields trước khi
class có #field (chương 4), memoize theo object key, cache DOM
node mà không leak memory khi node bị remove.
Bài tập
Bài 1 — Phân tích danh sách user
Cho mảng:
const users = [
{ name: 'An', age: 25 },
{ name: 'Bình', age: 17 },
{ name: 'Cường', age: 30 },
{ name: 'An', age: 21 },
{ name: 'Bảo', age: 19 },
];
Viết các tính toán sau (chain filter/map/reduce, không loop tay):
- Danh sách tên những user ≥ 21 tuổi.
- Tuổi trung bình (làm tròn 1 chữ số thập phân).
- Group user theo ký tự đầu của tên:
{ A: [...], B: [...], C: [...] }.
Đáp án
// 1.
const adultNames = users
.filter((u) => u.age >= 21)
.map((u) => u.name); // ['An', 'Cường', 'An']
// 2.
const avgAge = +(
users.reduce((s, u) => s + u.age, 0) / users.length
).toFixed(1); // 22.4
// 3.
const byLetter = users.reduce((acc, u) => {
const k = u.name[0];
(acc[k] ??= []).push(u);
return acc;
}, {});
// Hoặc dùng Object.groupBy (ES2024):
Object.groupBy(users, (u) => u.name[0]);
Bài 2 — Implement unique, chunk, zip không dùng lodash
unique(arr)trả mảng các phần tử unique (giữ thứ tự xuất hiện đầu tiên).chunk(arr, size)chia mảng thành mảng các "chunk" độ dàisize; chunk cuối có thể ngắn hơn. VDchunk([1,2,3,4,5], 2)→[[1,2],[3,4],[5]].zip(...arrs)ghép nhiều mảng cùng index:zip([1,2,3], ['a','b','c'])→[[1,'a'],[2,'b'],[3,'c']]. Nếu length khác, dừng ở mảng ngắn nhất.
Đáp án
const unique = (arr) => [...new Set(arr)];
const chunk = (arr, size) =>
Array.from(
{ length: Math.ceil(arr.length / size) },
(_, i) => arr.slice(i * size, i * size + size)
);
const zip = (...arrs) => {
const minLen = Math.min(...arrs.map((a) => a.length));
return Array.from({ length: minLen }, (_, i) => arrs.map((a) => a[i]));
};
unique([1, 2, 2, 3]); // [1, 2, 3]
chunk([1, 2, 3, 4, 5], 2); // [[1,2],[3,4],[5]]
zip([1, 2, 3], ['a', 'b', 'c']); // [[1,'a'],[2,'b'],[3,'c']]
Bài 3 — Generator Fibonacci + take
Viết:
function* fib()sinh ra dãy Fibonacci vô hạn: 0, 1, 1, 2, 3, 5, 8, 13, ...function* take(iter, n)lấy n phần tử đầu của iterable.
Test: [...take(fib(), 10)] → [0, 1, 1, 2, 3, 5, 8, 13, 21, 34].
Đáp án
function* fib() {
let [a, b] = [0, 1];
while (true) {
yield a;
[a, b] = [b, a + b];
}
}
function* take(iter, n) {
for (const v of iter) {
if (n-- <= 0) return;
yield v;
}
}
[...take(fib(), 10)];
// [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
Generator vô hạn hoạt động được vì lazy: yield dừng tới khi next() mới chạy tiếp. take ngắt mạch sớm.
Bài 4 — Refactor imperative → functional
Code dưới đang dùng loop + push. Refactor thành chain filter/map/reduce đọc rõ ràng hơn:
function totalDiscountedPrice(items) {
const instock = [];
for (const it of items) {
if (it.stock > 0) instock.push(it);
}
const prices = [];
for (const it of instock) {
prices.push(it.price * (1 - it.discount));
}
let total = 0;
for (const p of prices) total += p;
return total;
}
Đáp án
function totalDiscountedPrice(items) {
return items
.filter((it) => it.stock > 0)
.map((it) => it.price * (1 - it.discount))
.reduce((s, p) => s + p, 0);
}
Code ngắn hơn ~⅓, đọc được như "lọc còn hàng → tính giá sau giảm → cộng tổng".
Bài 5 — Generic groupBy(arr, keyFn)
Viết groupBy(arr, keyFn) nhận mảng + function tính key, trả object mỗi key → mảng phần tử match.
groupBy([1, 2, 3, 4, 5], (n) => n % 2 === 0 ? 'even' : 'odd');
// { odd: [1, 3, 5], even: [2, 4] }
groupBy(words, (w) => w.length);
// { 3: [...], 4: [...], ... }
Đáp án
function groupBy(arr, keyFn) {
return arr.reduce((acc, item) => {
const k = keyFn(item);
(acc[k] ??= []).push(item);
return acc;
}, {});
}
// ES2024 native: Object.groupBy(arr, keyFn) — kết quả tương tự.
// Hoặc Map.groupBy nếu cần key bất kỳ (object key).
Quiz
[1, 2, 3].map(parseInt) trả về?
Xem đáp án
[1, NaN, NaN]. Vì parseInt nhận (value, radix), map gọi callback với (value, index, array). Kết quả: parseInt('1', 0) = 1 (radix 0 = mặc định 10); parseInt('2', 1) = NaN (không có radix 1); parseInt('3', 2) = NaN ('3' không hợp lệ trong nhị phân).
Fix: [1, 2, 3].map((x) => parseInt(x)) hoặc .map(Number).
Array.from({ length: 3 }, (_, i) => i) trả về?
Xem đáp án
[0, 1, 2]. Array.from chấp nhận array-like (object có length) + map function. Đây là cách tạo array có index 0..n-1 không cần loop.
Gọi [].reduce((a, b) => a + b) (không initial value, trên array rỗng) — xảy ra gì?
Xem đáp án
TypeError: Reduce of empty array with no initial value. Reduce không có gì để khởi tạo accumulator. Quy tắc: luôn truyền initial value, vừa an toàn vừa tự document type.
for (const x of obj) với obj = { a: 1, b: 2 } — chạy được không?
Xem đáp án
Không. Plain object không có [Symbol.iterator] → throw TypeError: obj is not iterable. Dùng Object.entries(obj), Object.keys(obj) hoặc Object.values(obj) để có iterable.
So sánh performance lookup giữa arr.includes(x) và set.has(x)?
Xem đáp án
Set.has là O(1) (hash lookup). Array.includes là O(n) (duyệt tuần tự). Khi check membership trong vòng lặp / mảng lớn, chuyển sang Set có thể giảm thời gian từ O(n²) xuống O(n).
So sánh [...arr1, ...arr2] và arr1.concat(arr2) — khác gì?
Xem đáp án
Kết quả giống nhau (đều trả mảng mới, không mutate). Khác nhau ở flexibility: spread chèn được vào giữa [1, ...arr, 99], gom được iterable khác (Set, Map, generator) — concat chỉ ghép array. Performance: spread thường nhanh hơn cho mảng nhỏ; concat có thể nhanh hơn cho mảng rất lớn (engine tối ưu khác nhau).
arr.sort() không truyền comparator — sort theo gì?
Xem đáp án
Theo string — JS convert mọi phần tử sang string rồi so sánh code point. Đó là vì sao [10, 1, 2].sort() ra [1, 10, 2]. Với số phải truyền: arr.sort((a, b) => a - b).
WeakMap vs Map — key behavior khác chỗ nào?
Xem đáp án
Map giữ strong reference đến key — chặn GC, miễn map còn sống thì key còn sống. WeakMap giữ weak reference — nếu không nơi nào khác giữ key (chỉ WeakMap có), GC được phép thu hồi, entry biến mất khỏi map. Vì entries có thể biến mất bất kỳ lúc nào, WeakMap không iterable, không có .size, .keys(), .values(). Key phải là object, không nhận primitive.
Tổng kết
Sau chương 5, bạn nên đã master:
- Tạo array: literal,
Array.of,Array.from; tránhnew Array(n)sparse trap. - Mutate vs non-mutate: thuộc bảng. Trong React/Redux luôn dùng
toSorted,toReversed, spread copy. - map/filter/reduce: 4 pattern reduce kinh điển (sum, group, count, build object); luôn truyền initial value.
- for/of là default,
forEachtránh,forchỉ khi cần index thực sự. - find/some/every/includes: early return;
every([])= true. - flat / flatMap: depth,
Infinity. - sort default string — luôn truyền comparator.
- Spread vs Rest: cùng
..., khác ngữ cảnh (trái = rest, phải = spread). - Destructuring: rename, default, nested, param.
- Iteration protocol:
[Symbol.iterator]+next(). - Generator:
function*,yield, lazy, sequence vô hạn. - Set/Map/WeakSet/WeakMap: chọn đúng loại; Set dedupe; Map khi key bất kỳ; Weak khi cần GC-friendly metadata.
Kết nối
- Chương 6 (Async, Event Loop) — async iterator (
for await...of) là phần mở rộng tự nhiên của iteration protocol cho Promise. - Chương 7 (Error handling) — xử lý lỗi bên trong
reduce/mapmà không phá vỡ chain. - Chương 8+ (TypeScript) — type cho generic array/iterable, tuple, mapped types đối chiếu với
map/filter. - Dart Chương 6 (Collections) — đối chiếu
List/Set/MapDart, sync* / async* generator Dart.