Lesson 0.6.6 — Object Utilities
0. Metadata
| Field | Value |
|---|---|
| Stage | 0 — JavaScript Language Foundation |
| Module | 0.6 — Data Structures |
| Lesson | 0.6.6 |
| Competency | C01.6 — Arrays / Objects |
| Depth Target | L2–L3 |
| Prerequisites | Objects (0.6.5), Array Iteration (0.6.3), Reduce (0.6.4) |
| Estimated Cognitive Load | Medium |
1. Why This Exists (Vì sao cần học)
Bạn đã biết tạo object, truy cập property, xử lý nested object. Nhưng trong thực tế, bạn thường cần làm việc với toàn bộ object — lấy tất cả keys, biến object thành array để filter/map, hoặc gom array lại thành object.
Xét đoạn code:
const scores = { Alice: 85, Bob: 72, Carol: 91 };
// Tìm người có điểm >= 80
const passed = [];
for (const key in scores) {
if (scores[key] >= 80) {
passed.push(key);
}
}Vấn đề cốt lõi
for...in chạy được nhưng nói về cơ chế — duyệt key, kiểm tra hasOwnProperty (nếu bạn nhớ), push vào array. Object không có .filter() như array. Nhưng JavaScript cung cấp Object.keys, Object.values, Object.entries để biến object thành array trong một bước, sau đó bạn dùng toàn bộ arsenal array methods đã học.
Bài này dạy bạn: object ↔ array là một cầu nối. Object để lưu trữ theo key; array để xử lý theo sequence. Hiểu cách chuyển đổi giữa hai thế giới này là kỹ năng cốt lõi cho mọi data transformation.
2. Prerequisites (Yêu cầu đầu vào)
Trước khi học bài này, bạn cần:
- Tạo object, truy cập property bằng dot và bracket notation (0.6.5).
- Dùng
map,filter,reducetrên array (0.6.3, 0.6.4). - Hiểu array là object nhưng có
lengthvà index số (0.6.2). - Biết
constvà reference (0.2.2).
WARNING
Nếu bạn chưa chắc Object.keys({a: 1, b: 2}) trả về gì, hoặc chưa biết cách dùng reduce để biến array thành object, quay lại 0.6.4 và 0.6.5. Bài này kết hợp cả hai.
3. Learning Objectives (Mục tiêu học tập)
Sau bài này, bạn có thể:
- Sử dụng
Object.keys,Object.values,Object.entriesđể trích xuất dữ liệu từ object. - Phân biệt
Object.keys(chỉ own enumerable string keys) vàfor...in(bao gồm inherited). - Chuyển đổi object ↔ array bằng
entries+reduce(hoặcfromEntriesnếu environment hỗ trợ). - Viết function transform object values mà không mutate object gốc.
- Dự đoán thứ tự của keys trong kết quả.
- Nhận diện khi nào dùng
Object.valuesso vớiObject.entries.
4. Mental Model (Mô hình tư duy)
Mental Model
Object ↔ Array Bridge (Cầu nối hai chiều)
Object (Keyed) Array (Ordered)
┌─────────────┐ ┌─────────────┐
│ "a": 1 │ │ "a" │
│ "b": 2 │ Object.keys() │ "b" │
│ "c": 3 │ ─────────────→ │ "c" │
└─────────────┘ └─────────────┘
Object.entries()
─────────────────→
[ ["a", 1], ["b", 2], ["c", 3] ]
Array → Object (tái xây dựng)
entries.reduce((obj, [k, v]) => {
obj[k] = v;
return obj;
}, {})
Object.keys(obj) → ["a", "b", "c"] (chỉ keys)
Object.values(obj) → [1, 2, 3] (chỉ values)
Object.entries(obj) → [["a",1],...] (cả hai)
Quy tắc: Object không có map/filter. Muốn xử lý → qua Array.Quy tắc vàng:
- Object để tra cứu (lookup) và lưu trữ theo tên.
- Array để xử lý tuần tự (iteration, transform, filter).
Object.entrieslà cầu nối mạnh nhất vì mang cả key lẫn value sang array.Object.keyshữu ích khi bạn chỉ cần tên property.Object.valueshữu ích khi bạn chỉ cần giá trị (ví dụ: tính tổng).
5. Core Concepts (Các khái niệm cốt lõi)
Essential (Bắt buộc)
| Concept | Ý nghĩa |
|---|---|
Object.keys(obj) | Trả về array các own enumerable string keys của object. |
Object.values(obj) | Trả về array các values tương ứng với Object.keys. |
Object.entries(obj) | Trả về array các [key, value] pairs. |
Object.fromEntries(array) | Trả về object từ array các [key, value]. ES2019. |
| Own Property | Property thuộc trực tiếp object, không phải từ prototype chain. |
| Enumerable | Property có thể được liệt kê trong vòng lặp. |
Supporting (Hỗ trợ)
| Concept | Ý nghĩa |
|---|---|
| Enumeration Order | String keys số đi trước theo thứ tự số, sau đó string keys khác theo insertion order. |
for...in | Duyệt tất cả enumerable properties bao gồm inherited. Cần hasOwnProperty check. |
Awareness (Biết tồn tại)
| Concept | Lý do chưa đào sâu |
|---|---|
Object.getOwnPropertyNames | Liệt kê cả non-enumerable. Thuộc Stage 2 (Descriptors). |
Reflect.ownKeys | Liệt kê cả Symbol. Thuộc Stage 2. |
Out of Scope (Không thuộc bài này)
Object.assign,Object.freeze,Object.seal— Stage 2.- Prototype chain / inheritance — Stage 2.
- Property descriptors — Stage 2.
- Symbol keys — Stage 2.
6. Worked Example (Ví dụ phân tích từng bước)
Bài toán: Format tất cả giá trị trong object cấu hình.
const config = {
apiUrl: "https://api.example.com",
timeout: "5000",
retries: "3",
};Yêu cầu:
- Chuyển tất cả giá trị thành số nếu có thể parse.
- Giữ nguyên key.
- Không mutate
configgốc.
const parsed = {};
for (const key in config) {
if (config.hasOwnProperty(key)) {
const num = Number(config[key]);
parsed[key] = isNaN(num) ? config[key] : num;
}
}const parsed = Object.fromEntries(
Object.entries(config).map(([key, value]) => {
const num = Number(value);
return [key, isNaN(num) ? value : num];
}),
);Walkthrough
Step 1 — Object.entries(config) Trả về: [["apiUrl", "https://api.example.com"], ["timeout", "5000"], ["retries", "3"]]. Mỗi phần tử là một [key, value] pair.
Step 2 — .map(([key, value]) => ...) Destructuring [key, value] từng entry. map biến mỗi [key, value] thành [key, newValue]. Ở đây timeout và retries thành số.
Step 3 — Object.fromEntries(...) Biến array các [key, value] trở lại thành object. Kết quả: { apiUrl: "...", timeout: 5000, retries: 3 }.
Step 4 — So sánh với for...infor...in cần hasOwnProperty check để tránh inherited properties. Object.entries chỉ lấy own properties — an toàn hơn, ngắn gọn hơn.
Key Insight: Object.entries + array methods + Object.fromEntries tạo thành một pipeline: object → array → process → object. Không cần for...in.
7. Prediction Exercise (Bài tập dự đoán)
Đừng chạy code. Đọc và dự đoán output, sau đó giải thích tại sao.
Câu 1
const obj = { b: 2, a: 1 };
console.log(Object.keys(obj));Câu 2
const obj = { x: 10, y: 20 };
const values = Object.values(obj);
values.push(30);
console.log(obj);Câu 3
const obj = { a: 1, b: 2 };
const doubled = Object.entries(obj).map(([k, v]) => [k, v * 2]);
console.log(Object.fromEntries(doubled));Câu 4
const arr = [
["name", "Alice"],
["age", 24],
];
const obj = Object.fromEntries(arr);
console.log(obj.name);
console.log(obj["age"]);Câu 5
const obj = { 10: "a", 2: "b", 1: "c" };
console.log(Object.keys(obj));[Đáp án & Giải thích]
Câu 1: ["b", "a"] — Insertion order cho string keys
- Giải thích:
Object.keystrả về own enumerable string keys theo insertion order (trừ numeric keys). Trong object literal{ b: 2, a: 1 },bđược insert trướca, nên output là["b", "a"].
Câu 2: { x: 10, y: 20 } — Object.values trả về array mới
- Giải thích:
Object.valuestạo array mới.values.push(30)mutate arrayvalues, không affectobj.objkhông đổi.
Câu 3: { a: 2, b: 4 } — entries → map → fromEntries
- Giải thích:
entries→[["a", 1], ["b", 2]].mapdouble value →[["a", 2], ["b", 4]].fromEntries→ object mới.objgốc không đổi.
Câu 4: "Alice" và 24 — fromEntries tạo object
- Giải thích:
Object.fromEntriesbiến array[["name", "Alice"], ["age", 24]]thành{ name: "Alice", age: 24 }. Truy cập bình thường bằng dot và bracket.
Câu 5: ["1", "2", "10"] — Numeric keys sorted first
- Giải thích: Object keys là string. Keys trông như số nguyên được sắp xếp theo thứ tự số tăng dần:
1,2,10. Sau đó mới đến non-numeric theo insertion. Đây là behavior đặc biệt cần nhớ.
8. Implementation Lab (Bài lab thực hành)
Level 1 — Guided (Có hướng dẫn)
Viết function formatValues nhận object và một formatter function, trả về object mới với values đã format.
const prices = { apple: 100, banana: 50, cherry: 200 };
function formatValues(obj, formatter) {
// 1. Dùng Object.entries để lấy [key, value]
// 2. Dùng map để áp dụng formatter(value) cho mỗi value
// 3. Dùng Object.fromEntries để gom lại thành object
}
console.log(formatValues(prices, (v) => `$${v}`));
// { apple: "$100", banana: "$50", cherry: "$200" }Gợi ý
Object.entries(obj).map(([k, v]) => [k, formatter(v)]) rồi Object.fromEntries(...).
[Đáp án tham khảo]
function formatValues(obj, formatter) {
return Object.fromEntries(
Object.entries(obj).map(([key, value]) => {
return [key, formatter(value)];
}),
);
}
const prices = { apple: 100, banana: 50, cherry: 200 };
console.log(formatValues(prices, (v) => `$${v}`));
// { apple: "$100", banana: "$50", cherry: "$200" }
console.log(prices);
// { apple: 100, banana: 50, cherry: 200 } ✅ không đổiGiải thích:
Object.entriestạo array các[key, value].mapbiến mỗi pair thành[key, formatter(value)].Object.fromEntriesgom array pairs trở lại object.pricesgốc không đổi vì ta tạo object mới.
Level 2 — Partial Scaffold (Khung sẵn)
Hoàn thành function filterObject để trả về object mới chỉ chứa các entry pass predicate.
function filterObject(obj, predicate) {
// predicate nhận (key, value) → boolean
return Object.fromEntries(
Object._______(obj)._______(([key, value]) => {
return predicate(key, value);
}),
);
}
const scores = { Alice: 85, Bob: 72, Carol: 91 };
console.log(filterObject(scores, (k, v) => v >= 80));
// { Alice: 85, Carol: 91 }[Đáp án tham khảo]
function filterObject(obj, predicate) {
return Object.fromEntries(
Object.entries(obj).filter(([key, value]) => {
return predicate(key, value);
}),
);
}
const scores = { Alice: 85, Bob: 72, Carol: 91 };
console.log(filterObject(scores, (k, v) => v >= 80));
// { Alice: 85, Carol: 91 }Giải thích:
Object.entrieslấy tất cả[key, value].filtergiữ lại pair màpredicatetrả về truthy.Object.fromEntriesgom lại thành object.- Pattern này tương đương
Array.prototype.filternhưng cho object.
Level 3 — Independent (Tự viết)
Viết các function sau (không dùng thư viện, không mutate input):
// 1. invertObject(obj) → Đảo key và value. Giả sử values là unique và primitive.
// Ví dụ: invertObject({a: 1, b: 2}) → {1: "a", 2: "b"}
// 2. mapKeys(obj, keyMapper) → Trả về object mới với keys được biến đổi, giữ nguyên values
// Ví dụ: mapKeys({a: 1, b: 2}, k => k.toUpperCase()) → {A: 1, B: 2}
// 3. sumValues(obj) → Tính tổng tất cả values (giả sử values là number)
// Ví dụ: sumValues({x: 10, y: 20}) → 30
// Sử dụng:
const data = { a: 1, b: 2, c: 3 };
console.log(invertObject(data));
// { "1": "a", "2": "b", "3": "c" }
console.log(mapKeys(data, (k) => k + k));
// { aa: 1, bb: 2, cc: 3 }
console.log(sumValues(data));
// 6[Đáp án tham khảo]
function invertObject(obj) {
return Object.fromEntries(
Object.entries(obj).map(([key, value]) => [value, key]),
);
}
function mapKeys(obj, keyMapper) {
return Object.fromEntries(
Object.entries(obj).map(([key, value]) => [keyMapper(key), value]),
);
}
function sumValues(obj) {
return Object.values(obj).reduce((sum, val) => sum + val, 0);
}
const data = { a: 1, b: 2, c: 3 };
console.log(invertObject(data));
// { "1": "a", "2": "b", "3": "c" }
console.log(mapKeys(data, (k) => k + k));
// { aa: 1, bb: 2, cc: 3 }
console.log(sumValues(data));
// 6Giải thích:
invertObject: Đảo[key, value]thành[value, key]. Key mới là string (vì object keys luôn là string hoặc Symbol).mapKeys: Biến đổi key quakeyMapper, giữ value.sumValues:Object.valueslấy array values,reducetính tổng. Không cần entries vì không cần key.- Cả ba đều không mutate
data.
9. Edge Cases (Các trường hợp ngoại lệ)
Object.keys không bao gồm inherited properties
function Person(name) {
this.name = name;
}
Person.prototype.greet = function () {};
const alice = new Person("Alice");
console.log(Object.keys(alice)); // ["name"]
console.log("greet" in alice); // trueTại sao: Object.keys chỉ liệt kê own enumerable properties. greet nằm trên prototype, không phải own property.
Cách nhận biết: for...in thấy property mà Object.keys không thấy.
Cách xử lý: Dùng Object.keys khi chỉ cần own properties. Dùng hasOwnProperty với for...in nếu buộc phải dùng for...in.
Numeric keys sorted, non-numeric by insertion
const obj = { b: 1, 10: 2, a: 3, 2: 4 };
console.log(Object.keys(obj)); // ["2", "10", "b", "a"]Tại sao: Integer-like keys (0, 1, 2, 10) được sắp xếp tăng dần trước. String keys khác giữ insertion order.
Cách nhận biết: Object tạo với string keys nhưng có key số thì thứ tự bất ngờ.
Cách xử lý: Không dựa vào thứ tự keys nếu object có mixed numeric/string keys. Nếu cần order chắc chắn, dùng Map (Stage 2) hoặc array.
Object.fromEntries với duplicate keys
const entries = [
["a", 1],
["b", 2],
["a", 3],
];
const obj = Object.fromEntries(entries);
console.log(obj); // { a: 3, b: 2 }Tại sao: Key sau đè key trước. Không có lỗi.
Cách nhận biết: Object kết quả thiếu entry đầu tiên.
Cách xử lý: Nếu cần giữ tất cả, không dùng object (dùng array hoặc Map).
Object.entries với non-object argument
console.log(Object.entries("ab")); // [["0", "a"], ["1", "b"]]
console.log(Object.entries(42)); // []Tại sao: Non-object được coerce thành object. String thành String wrapper object với index properties. Number thành Number wrapper object không có enumerable properties.
Cách nhận biết: Kết quả bất ngờ khi truyền primitive.
Cách xử lý: Đảm bảo argument là object. Nếu cần handle string, aware behavior này.
Mutate value trong map vẫn affect gốc
const obj = { a: { x: 1 }, b: { x: 2 } };
const next = Object.fromEntries(Object.entries(obj).map(([k, v]) => [k, v]));
next.a.x = 99;
console.log(obj.a.x); // 99Tại sao: Object.fromEntries với map chỉ copy array pairs, không deep clone values. Object nested vẫn shared reference.
Cách nhận biết: Object mới nhưng nested object vẫn bị thay đổi.
Cách xử lý: Shallow copy awareness. Deep clone sẽ học ở 0.6.8/Stage 2.
10. Debug Lab (Bài lab gỡ lỗi)
Symptom (Triệu chứng): Function sanitizeConfig trả về object "sạch" nhưng object gốc cũng bị mất property.
function sanitizeConfig(config) {
const keys = Object.keys(config);
const result = {};
for (const key of keys) {
if (key.startsWith("_")) {
delete config[key];
} else {
result[key] = config[key];
}
}
return result;
}
const cfg = { apiUrl: "...", _secret: "abc", timeout: 5000 };
const clean = sanitizeConfig(cfg);
console.log(clean); // { apiUrl: "...", timeout: 5000 } ✅
console.log(cfg._secret); // undefined ❌ — gốc bị xóa!Reproduction (Tái hiện lỗi): Chạy code trên.
Evidence (Bằng chứng):
cfg._secretthànhundefinedsau khi gọisanitizeConfig.cleanvàcfglà object khác nhau nhưngcfgbị mutate.
Hypothesis (Giả thuyết): delete config[key] mutate input config trực tiếp. Mục đích là tạo object mới sạch, nhưng code vừa mutate gốc vừa tạo mới.
Verification (Xác minh):
console.log("_secret" in cfg); // false — đã bị xóaRoot Cause (Nguyên nhân gốc rễ): Nhầm lẫn giữa "tạo object mới không có key" và "xóa key khỏi object cũ". delete trong loop làm mất dữ liệu gốc.
Fix (Sửa):
function sanitizeConfig(config) {
return Object.fromEntries(
Object.entries(config).filter(([key]) => !key.startsWith("_")),
);
}Prevention (Phòng ngừa):
- Nghi ngờ mọi
deletetrong function nhận object từ bên ngoài. - Muốn "loại bỏ" key → tạo object mới không có key đó (filter), đừng xóa khỏi gốc.
Object.entries+filter+fromEntrieslà pattern an toàn cho "subset object".
11. Design Exercise (Bài tập thiết kế giải pháp)
Bạn cần tìm key có value lớn nhất trong object.
function maxKey(obj) {
return Object.entries(obj).reduce(
(max, [k, v]) => {
return v > max.value ? { key: k, value: v } : max;
},
{ key: null, value: -Infinity },
);
}function maxKey(obj) {
const sorted = Object.keys(obj).sort((a, b) => obj[b] - obj[a]);
return { key: sorted[0], value: obj[sorted[0]] };
}Câu hỏi:
- Option A duyệt array mấy lần?
- Option B có vấn đề gì về performance?
- Nếu object có 10,000 entries, bạn chọn cái nào?
[Đáp án tham khảo]
Bạn nghĩ:
- Option A duyệt một lần qua
entries.reducetính toán tại chỗ. - Option B
sortduyệt nhiều lần (O(n log n)) và tạo array keys mới rồi sắp xếp toàn bộ. Chỉ cần tìm max nhưng lại sort hết. - Option A. O(n) vs O(n log n). Sort là overkill cho "find max".
- Option A duyệt một lần qua
Kết luận: Chọn algorithm phù hợp intent.
reducecho aggregation single-pass;sortcho ordering. Không dùng sort để tìm max.
12. Production Scenario (Tình huống thực tế)
Context: API trả về object lồng nhau:
const response = {
data: {
user: { name: "Alice", role: "admin" },
settings: { theme: "dark" },
tags: ["developer", "admin"],
},
};Developer muốn flatten thành object một cấp với prefix:
{
"data.user.name": "Alice",
"data.user.role": "admin",
"data.settings.theme": "dark",
"data.tags": ["developer", "admin"]
}Code hiện tại:
const result = {};
for (const key in obj) {
const value = obj[key];
const newKey = prefix ? `${prefix}.${key}` : key;
if (typeof value === "object") {
const nested = flatten(value, newKey);
for (const [k, v] of Object.entries(nested)) {
result[k] = v;
}
} else {
result[newKey] = value;
}
}
return result;
}Symptom: flatten(response) có thể tạo ra các key không mong muốn, ví dụ array bị flatten thành các index:
{
"data.user.name": "Alice",
"data.user.role": "admin",
"data.settings.theme": "dark",
"data.tags.0": "developer",
"data.tags.1": "admin"
}Ngoài ra, nếu object có inherited enumerable properties, for...in cũng có thể đưa các key không thuộc trực tiếp object vào kết quả.
Constraint: Không được thay đổi cấu trúc của API response. Array phải được giữ nguyên như một value.
Câu hỏi:
for...inở đây có rủi ro gì nếu chỉ muốn duyệt các properties thuộc trực tiếp object?typeof value === "object"có bắt được array không? Hậu quả là gì?Có trường hợp nào khác mà
typeof value === "object"gây ra hành vi không mong muốn?Viết lại function bằng
Object.entriesđể an toàn hơn và giữ array như một leaf value.
[Đáp án tham khảo]
Bạn nghĩ:
for...incó thể duyệt cả inherited enumerable properties từ prototype, không chỉ properties thuộc trực tiếp object. Vì vậy các key không thuộc API response thực tế có thể bị đưa vào kết quả.typeof [] === "object"làtrue. Vì vậy array sẽ bị coi như object và tiếp tục đệ quy["developer", "admin"]có thể trở thành{ "data.tags.0": "developer", "data.tags.1": "admin" }. Trong bài toán này đây là hành vi không mong muốn vì requirement là giữ nguyên array.nullcũng có typeobject. Vì vậy chỉ kiểm tratypeof value === "object"là chưa đủ để xác định value có phải nested object hay không.Có thể viết lại bằng
Object.entries()và kiểm tra rõnull+ array:jsfunction flatten(obj, prefix = "") { const result = {}; for (const [key, value] of Object.entries(obj)) { const newKey = prefix ? `${prefix}.${key}` : key; if ( value !== null && typeof value === "object" && !Array.isArray(value) ) { const nested = flatten(value, newKey); for (const [k, v] of Object.entries(nested)) { result[k] = v; } } else { result[newKey] = value; } } return result; }
Kết quả:
js{ "data.user.name": "Alice", "data.user.role": "admin", "data.settings.theme": "dark", "data.tags": ["developer", "admin"] }Bài học:
Object.entries()chỉ lấy own enumerable properties nên phù hợp hơnfor...inkhi chỉ muốn duyệt dữ liệu trực tiếp của object. Đồng thời, không nên dùng đơn thuầntypeof value === "object"để xác định nested object vì array vànullcũng thỏa điều kiện này. Khi logic nghiệp vụ cần phân biệt object, array vànull, hãy kiểm tra rõ từng trường hợp.
13. AI-assisted Exercise (Bài tập với AI)
Level B — Challenge
Tự trả lời trước: Viết đoạn code dùng
for...inđể liệt kê keys của object. Dự đoán có gì khác biệt so vớiObject.keys.jsfunction Person(name) { this.name = name; } Person.prototype.greet = function () {}; const p = new Person("Alice");Hỏi AI: "Liệt kê tất cả keys của
pbằngfor...invàObject.keys. Tại sao kết quả khác nhau?"So sánh câu trả lời AI với nhận định của bạn. AI có nhấn mạnh own property vs inherited enumerable property không?
Verify bằng MDN: tìm
Object.keysvàfor...intrên MDN, đọc phần mô tả về prototype chain.
Gợi ý
AI thường biết for...in bao gồm prototype nhưng đôi khi giải thích mơ hồ bằng cách nói "for...in loops over all enumerable properties". Nếu AI không chỉ ra rằng Object.keys là lựa chọn an toàn mặc định và for...in cần hasOwnProperty check để tránh lẫn inherited, bạn đã tìm ra điểm mù. AI cũng có thể không nhắc đến Object.entries/Object.values có cùng behavior với Object.keys (chỉ own enumerable string keys).
Đáp án tham khảo
Bạn nghĩ:
for...inliệt kê cảname(own) vàgreet(inherited từ prototype).Object.keyschỉ liệt kêname(own enumerable).Object.entriesvàObject.valuescũng chỉ own enumerable.AI trả lời (typical):
for...initerates over all enumerable properties including inherited ones.Object.keysonly returns own enumerable properties.Object.keys(p)will only return["name"].for...inwill returnnameandgreet.
So sánh: AI thường đúng về behavior nhưng đôi khi không giải thích hậu quả production: nếu codebase thêm method vào
Object.prototype(polyfill cũ hoặc library),for...inkhông filter sẽ lẫn keys như"toJSON","greet"vào object iteration của bạn.Điểm AI nói sai hoặc quá mơ hồ: "for...in includes inherited properties" — đúng nhưng chưa đủ. AI hiếm khi nói rõ: "Object.keys là canonical way để iterate object an toàn. for...in là legacy pattern cần hasOwnProperty guard. Trong modern JS, dùng Object.entries để thay thế for...in trong hầu hết trường hợp."
Kết luận: Nếu bạn chỉ ra được rằng
Object.keys/entries/valueslà deliberate design để tránh prototype pollution, vàfor...inchỉ dùng khi bạn cố ý muốn inherited properties, bạn đã hiểu sâu hơn AI. Concept này sẽ quay lại ở Stage 2 (Prototype chain, property descriptors) và Stage 11 (Prototype pollution security).
14. Teach Back (Dạy lại)
Giả sử một junior developer hỏi bạn:
"Anh/chị ơi, tại sao object không có
.map()như array? Em phải viếtObject.keys(obj).map(...)rồi còn phải tra lạiobj[key]— rườm rà quá. Tại sao JavaScript không cho object.map()luôn?"
Hãy giải thích trong 2 phút, dùng đúng terminology: keyed collection, sequence, Object.entries, bridge.
Mô phỏng
- Bạn nói: Array là sequence — dãy có thứ tự, index là số.
mapáp dụng cho mỗi vị trí theo thứ tự. Object là keyed collection — không có thứ tự đảm bảo (trừ insertion order), và mục đích chính là tra cứu theo tên, không phải xử lý tuần tự.
JavaScript cung cấp Object.entries như một cầu nối: object → array → array methods → object lại. Object.entries(obj).map(...) rõ ràng hơn vì nói: "Tôi đang biến object thành sequence để xử lý." Nếu object có .map(), bạn sẽ không biết đang map theo key hay value hay cả hai. entries buộc bạn phải explicit.
Kết luận: Không phải thiếu sót — đây là separation of concerns. Object lưu trữ; array xử lý. entries là cầu nối.
💡 Tưởng tượng object như một tủ đựng hồ sơ theo tên. Array như một dây chuyền lắp ráp. Bạn không lắp ráp trực tiếp trong tủ — bạn lấy hồ sơ ra, đưa lên dây chuyền, xử lý xong, cất lại tủ.
Object.entrieschính là "lấy ra",Object.fromEntrieslà "cất lại".
Gợi ý đánh giá bản thân
- Đồng nghiệp có hiểu tại sao object không cần
.map()không? - Bạn có tránh được việc nói "JavaScript thiết kế tệ" không? (Đây là intentional design, không phải bug.)
- Nếu đồng nghiệp hỏi: "Vậy khi nào em dùng
Object.keysthay vìObject.entries?" — bạn trả lời được không? (Gợi ý: chỉ cần key (ví dụ: kiểm tra tồn tại) hoặc chỉ cần value (tính toán) thì không cần cả cặp.)
15. Assessment (Đánh giá)
| Objective (Mục tiêu) | Hình thức đánh giá | Task |
|---|---|---|
| Use Object.keys/values/entries (Sử dụng utilities) | Implementation (Thực hành) | Implementation Lab Level 1 |
| Distinguish Object.keys vs for...in (Phân biệt keys/for...in) | Prediction (Dự đoán) | Prediction + AI Exercise |
| Transform object ↔ array (Chuyển đổi object/array) | Implementation (Thực hành) | Implementation Lab Level 2–3 |
| Filter object without mutating (Lọc object không mutate) | Debug Lab | Debug Lab Section |
| Choose reduce vs sort for aggregation (Chọn algorithm) | Design Decision | Design Exercise |
| Explain object/array bridge | Teach Back | Teach Back Section |
16. Exit Criteria (Tiêu chí qua bài)
- [ ] Có thể sử dụng
Object.keys,Object.values,Object.entriesđể trích xuất dữ liệu từ object. - [ ] Có thể phân biệt
Object.keys(own enumerable only) vàfor...in(includes inherited enumerable). - [ ] Có thể chuyển object thành array entries, xử lý bằng
map/filter/reduce, rồi chuyển lại object bằngObject.fromEntrieshoặcreduce. - [ ] Có thể viết function transform object values (format, filter, map keys) mà không mutate object gốc.
- [ ] Có thể dự đoán thứ tự keys khi object có numeric-like keys.
- [ ] Có thể debug lỗi mutate object gốc qua
deletetrong iteration. - [ ] Có thể chọn
Object.keysvsObject.entriesvsObject.valuesphù hợp với intent. - [ ] Có thể nhận diện
typeof [] === "object"và xử lý array khác object khi cần.
17. Spiral Connection (Liên kết xoắn ốc)
Previous (Trước): Objects (0.6.5) — bạn đã biết tạo object, truy cập property, xử lý nested. Giờ bạn học cách xử lý toàn bộ object như một unit bằng cách chuyển sang array.
Current (Hiện tại): Object Utilities —
Object.keys,values,entries,fromEntries. Object ↔ Array bridge. Own vs inherited properties.
Next (Tiếp theo):
- 0.6.7 (Destructuring) — Trích xuất nhiều property từ object/array cùng lúc.
const { keys } = objlà shorthand choobj.keys.- 0.6.8 (Spread & Shallow Copy) —
{ ...obj }tạo object mới. Kết hợp vớientriesđể transform object mà không mutate.- Stage 2 (Object Model) — Prototype chain: tại sao
for...inthấy inherited properties. Property descriptors: enumerable, configurable, writable.Object.getOwnPropertyNames.- Stage 6 (TypeScript) —
keyof,Record<K, V>: type-level object utilities.- Stage 8 (React) — Props spread:
<Component {...props} />. Object destructuring trong function parameters là pattern bắt buộc.