Lesson 0.6.5 — Objects
0. Metadata
| Field | Value |
|---|---|
| Stage | 0 — JavaScript Language Foundation |
| Module | 0.6 — Data Structures |
| Lesson | 0.6.5 |
| Competency | C01.6 — Arrays / Objects |
| Depth Target | L2–L3 |
| Prerequisites | Reduce (0.6.4), Arrays (0.6.2), Primitive vs Object (0.2.1/0.2.4), Variables & Bindings (0.2.2) |
| Estimated Cognitive Load | Medium |
1. Why This Exists (Vì sao cần học)
Bạn đã biết array: một dãy có thứ tự, truy cập bằng số. Nhưng trong thực tế, hầu hết dữ liệu có cấu trúc: một user không phải là dãy số, mà là tập hợp các thuộc tính có tên — name, email, role.
Xét đoạn code:
const field = "email";
const user = { name: "Alice", email: "alice@example.com" };
console.log(user.field);Vấn đề cốt lõi
user.field không phải user.email. Nó tìm một key tên là "field" — không tồn tại, trả về undefined. Đây là lỗi cực kỳ phổ biến: nhầm lẫn giữa dot notation (tên key cố định, viết trong code) và bracket notation (key động, từ biến).
Bài này xây mental model về object: mutable collection của key-value pairs. Bạn phải biết khi nào dùng dấu chấm, khi nào dùng dấu ngoặc vuông, và tại sao nested object lại là "shallow reference trap" giống array.
2. Prerequisites (Yêu cầu đầu vào)
Trước khi học bài này, bạn cần:
- Phân biệt primitive và object: object là mutable, reference type (0.2.1, 0.2.4).
- Biết array là object, hiểu reference sharing và mutation (0.6.2).
- Biết
constcấm reassignment nhưng không cấm mutation (0.2.2). - Biết callback và arrow function cơ bản (0.5.3).
WARNING
Nếu bạn chưa chắc tại sao const a = {x: 1}; a.x = 2 hợp lệ, quay lại 0.2.2 và 0.6.2. Object mutation là trung tâm của bài này.
3. Learning Objectives (Mục tiêu học tập)
Sau bài này, bạn có thể:
- Tạo object bằng literal và truy cập property bằng dot notation và bracket notation.
- Phân biệt dot notation (key cố định, identifier) và bracket notation (key động, expression).
- Sử dụng computed property name khi tạo object với key động.
- Thêm, sửa, xóa property trên object và nhận diện hậu quả của
delete. - Nhận diện nested object reference trap (mutate nested object ảnh hưởng object gốc).
- Viết function xử lý object mà không mutate input ngoài ý muốn.
4. Mental Model (Mô hình tư duy)
Mental Model
Object = Tủ hồ sơ có nhãn (Keyed Collection)
Key (string/Symbol) Value
┌──────────────┐ ┌─────┐
│ "name" │ ──→ │"Alice"│
│ "email" │ ──→ │"a@e" │
│ "profile" │ ──→ │ { } │ ←── Object khác (nested)
└──────────────┘ └─────┘
Dot Notation (Dấu chấm)
obj.name → Tìm key "name". Key phải là identifier hợp lệ.
obj.123 → ❌ SyntaxError (không phải identifier)
Bracket Notation (Dấu ngoặc vuông)
obj["name"] → Tìm key "name". Expression bên trong được evaluate.
obj[key] → Tìm key bằng giá trị của biến `key`.
obj["123"] → ✅ Hợp lệ (key là string "123")
obj[123] → ✅ Tương đương obj["123"]
Nested Object = Tủ hồ sơ bên trong tủ hồ sơ
obj.profile.avatar
obj["profile"]["avatar"]
obj.profile["avatar"] → Hỗn hợp, hợp lệ
Lưu ý: obj.profile trả về reference đến object nested.
Sửa obj.profile.avatar = "new.jpg" → Sửa object gốc!Quy tắc vàng:
- Dot notation khi bạn biết chắc tên key lúc viết code.
- Bracket notation khi key đến từ biến, tính toán, hoặc chứa ký tự đặc biệt (dấu cách, dấu gạch ngang, số đầu tiên).
deletexóa property nhưng không giải phóng nested object khỏi reference.
5. Core Concepts (Các khái niệm cốt lõi)
Essential (Bắt buộc)
| Concept | Ý nghĩa |
|---|---|
| Object Literal | { name: "Alice", age: 24 } — tạo object với key-value pairs. |
| Dot Notation | obj.name — truy cập property có key là identifier hợp lệ. |
| Bracket Notation | obj["name"] hoặc obj[key] — truy cập bằng string expression. |
| Computed Property Name | { [expression]: value } — key được tính toán khi tạo object. |
| Add/Update | obj.newKey = value hoặc obj["newKey"] = value. |
delete | delete obj.key — xóa property. Trả về true (trừ non-configurable). |
in operator | "key" in obj — kiểm tra property tồn tại (kể cả value là undefined). |
| Nested Object | Object là value của một property khác. Truy cập chuỗi: obj.a.b. |
Supporting (Hỗ trợ)
| Concept | Ý nghĩa |
|---|---|
undefined vs missing | obj.x là undefined nếu x không tồn tại HOẶC tồn tại nhưng giá trị là undefined. |
| Property enumeration order | String keys số đi trước, sau đó string keys tạo theo thứ tự insertion. Không cần đào sâu. |
Awareness (Biết tồn tại)
| Concept | Lý do chưa đào sâu |
|---|---|
hasOwnProperty | Kiểm tra property thuộc object (không phải prototype). Thuộc Stage 2 (Prototype). |
Object.defineProperty | Tạo property với descriptor (writable, enumerable, configurable). Thuộc Stage 2. |
| Symbol keys | Symbol là primitive dùng làm key. Học ở Stage 2. |
Out of Scope (Không thuộc bài này)
Object.keys,Object.values,Object.entries— 0.6.6.- Destructuring — 0.6.7.
- Spread & Shallow Copy — 0.6.8.
- Prototype chain / inheritance — Stage 2.
thistrong object method — Stage 2.- Getters/Setters — Stage 2.
6. Worked Example (Ví dụ phân tích từng bước)
Bài toán: Xây dựng object cấu hình từ danh sách biến môi trường.
const envVars = [
{ key: "API_URL", value: "https://api.example.com" },
{ key: "API_TIMEOUT", value: "5000" },
{ key: "FEATURE_FLAG_X", value: "true" }
];Yêu cầu:
- Biến thành object
{ API_URL: "...", API_TIMEOUT: "...", FEATURE_FLAG_X: "..." }. - Không mutate array gốc (đã học ở 0.6.2–0.6.4).
- Hỗ trợ key động (từ biến
keytrong array).
function buildConfig(vars) {
const config = {};
for (const v of vars) {
config.v.key = v.value; // ❌ config.v là undefined
}
return config;
}function buildConfig(vars) {
return vars.reduce((config, v) => {
config[v.key] = v.value;
return config;
}, {});
}Walkthrough
Step 1 — Phân tích lỗi Beforeconfig.v.key tìm property tên là "v" trên config, sau đó tìm "key" trên kết quả. config không có property "v" → undefined.key → TypeError.
Step 2 — Bracket notationconfig[v.key] evaluate v.key trước (ví dụ "API_URL"), rồi truy cập property có tên đó. Đây là cách duy nhất khi key đến từ biến.
Step 3 — reduce Dùng reduce (0.6.4) để tích lũy object. Initial value {}. Mỗi bước thêm một key. Return config để lan truyền.
Step 4 — Computed property name (alternative — preview 0.6.8) Nếu tạo object mới mỗi bước (không mutate accumulator):
return vars.reduce((config, v) => ({
...config, // Object spread — sẽ học ở 0.6.8
[v.key]: v.value
}), {});[v.key] là computed property name trong object literal. Key được tính toán từ expression.
Key Insight: Dot notation là "hard-coded key". Bracket notation là "dynamic key". Nhầm lẫn hai cái là bug #1 khi xử lý object từ API hoặc form.
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 key = "name";
const user = { name: "Alice" };
console.log(user.key);
console.log(user[key]);Câu 2
const obj = { a: 1, b: 2 };
delete obj.a;
console.log(obj.a);
console.log("a" in obj);Câu 3
const user = {
profile: { age: 24 }
};
const p = user.profile;
p.age = 30;
console.log(user.profile.age);Câu 4
const obj = {};
const key = "123";
obj.key = "dot";
obj[key] = "bracket";
console.log(obj.key);
console.log(obj[key]);
console.log(obj["123"]);Câu 5
const settings = { theme: undefined };
console.log(settings.theme);
console.log("theme" in settings);
console.log(settings.font);
console.log("font" in settings);[Đáp án & Giải thích]
Câu 1: undefined và "Alice" — Dot vs Bracket
- Giải thích:
user.keytìm property tên"key"— không tồn tại →undefined.user[key]evaluatekeythành"name"→ tìm property"name"→"Alice".
Câu 2: undefined và false — delete
- Giải thích:
delete obj.axóa property"a".obj.atruy cập property không tồn tại →undefined."a" in objkiểm tra sự tồn tại →false. Khác vớiobj.a === undefined(có thể property tồn tại nhưng giá trị làundefined).
Câu 3: 30 — Nested reference sharing
- Giải thích:
p = user.profilecopy reference đến object nested.p.age = 30mutate object đó.user.profilevẫn trỏ đến cùng object →30. Giống array shallow copy trap.
Câu 4: "dot", "bracket", "bracket" — Key là string "123"
- Giải thích:
obj.key = "dot"tạo property"key".obj[key]vớikey = "123"tạo property"123".obj["123"]truy cập property"123"→"bracket".obj.123là SyntaxError (dot notation không hợp lệ với số), nhưngobj["123"]hợp lệ.
Câu 5: undefined, true, undefined, false — in vs undefined
- Giải thích:
settings.themelàundefined(property tồn tại, giá trị làundefined)."theme" in settingslàtrue(property tồn tại).settings.fontlàundefined(property không tồn tại)."font" in settingslàfalse. Đây là cách phân biệt "thiếu property" và "property có giá trị undefined".
8. Implementation Lab (Bài lab thực hành)
Level 1 — Guided (Có hướng dẫn)
Viết function buildLookup nhận array object và tên key, trả về object lookup.
const users = [
{ id: "u1", name: "Alice" },
{ id: "u2", name: "Bob" }
];
function buildLookup(items, keyField) {
// Dùng reduce
// Mỗi item: acc[item[keyField]] = item
// Return acc
}
console.log(buildLookup(users, "id"));
// { u1: { id: "u1", name: "Alice" }, u2: { id: "u2", name: "Bob" } }Gợi ý
item[keyField] dùng bracket notation vì keyField là biến. Không được viết item.keyField.
[Đáp án tham khảo]
function buildLookup(items, keyField) {
return items.reduce((acc, item) => {
acc[item[keyField]] = item;
return acc;
}, {});
}
const users = [
{ id: "u1", name: "Alice" },
{ id: "u2", name: "Bob" }
];
console.log(buildLookup(users, "id"));
// {
// u1: { id: "u1", name: "Alice" },
// u2: { id: "u2", name: "Bob" }
// }
console.log(buildLookup(users, "name"));
// {
// Alice: { id: "u1", name: "Alice" },
// Bob: { id: "u2", name: "Bob" }
// }Giải thích:
item[keyField]là bracket notation với key động.reducetích lũy object từ array.accbắt đầu là{}, mỗi bước thêm một key.- Function tái sử dụng được với bất kỳ field nào.
Level 2 — Partial Scaffold (Khung sẵn)
Hoàn thành function safeGet để truy cập nested property không bị lỗi nếu path không tồn tại.
function safeGet(obj, path) {
// path là mảng các key, ví dụ ["user", "profile", "name"]
// Duyệt từng key, nếu key không tồn tại thì return undefined
// Nếu tồn tại hết, return giá trị cuối
let current = ___;
for (const key of path) {
if (current === null || current === undefined) {
return _______;
}
current = current[_____];
}
return _______;
}
const data = { user: { profile: { name: "Alice" } } };
console.log(safeGet(data, ["user", "profile", "name"])); // "Alice"
console.log(safeGet(data, ["user", "settings", "theme"])); // undefined
console.log(safeGet(null, ["user"])); // undefined[Đáp án tham khảo]
function safeGet(obj, path) {
let current = obj;
for (const key of path) {
if (current === null || current === undefined) {
return undefined;
}
current = current[key];
}
return current;
}
const data = { user: { profile: { name: "Alice" } } };
console.log(safeGet(data, ["user", "profile", "name"]));
// "Alice"
console.log(safeGet(data, ["user", "settings", "theme"]));
// undefined
console.log(safeGet(null, ["user"]));
// undefinedGiải thích:
currentbắt đầu làobj.- Vòng lặp duyệt từng key trong
path. current === null || current === undefinedngăn lỗiCannot read properties of undefined.current[key]dùng bracket notation vìkeylà biến trong vòng lặp.- Pattern này gọi là "optional chaining thủ công" — ES2020 có
?.nhưng ở Stage 0 ta viết từ đầu để hiểu mechanism.
Level 3 — Independent (Tự viết)
Viết các function sau (không dùng thư viện):
// 1. pick(obj, keys) → Trả về object mới chỉ chứa các key được chỉ định
// Ví dụ: pick({a: 1, b: 2, c: 3}, ["a", "c"]) → {a: 1, c: 3}
// 2. omit(obj, keys) → Trả về object mới không chứa các key được chỉ định
// Ví dụ: omit({a: 1, b: 2, c: 3}, ["b"]) → {a: 1, c: 3}
// 3. renameKey(obj, oldKey, newKey) → Trả về object mới với key được đổi tên, giữ nguyên các key khác
// Ví dụ: renameKey({name: "Alice", age: 24}, "name", "fullName")
// → {fullName: "Alice", age: 24}
// Sử dụng:
const user = { id: 1, name: "Alice", password: "secret", role: "admin" };
console.log(pick(user, ["id", "name"]));
// { id: 1, name: "Alice" }
console.log(omit(user, ["password"]));
// { id: 1, name: "Alice", role: "admin" }
console.log(renameKey(user, "name", "fullName"));
// { id: 1, fullName: "Alice", password: "secret", role: "admin" }Gợi ý
pick: Lặp quakeys, lấyobj[key]cho mỗi key.omit: Dùngfor...inđể duyệt tất cả keys, bỏ qua key trong danh sách loại bỏ. DùngSet(đã gặp ở 0.6.3) để kiểm tra nhanh.renameKey: Dùngfor...inđể duyệt keys. Khi gặpoldKey, gán giá trị chonewKeythay thế. Các key khác copy nguyên.
[Đáp án tham khảo]
function pick(obj, keys) {
const result = {};
for (const key of keys) {
if (key in obj) {
result[key] = obj[key];
}
}
return result;
}
function omit(obj, keys) {
const keySet = new Set(keys);
const result = {};
for (const key in obj) {
if (!keySet.has(key)) {
result[key] = obj[key];
}
}
return result;
}
function renameKey(obj, oldKey, newKey) {
const result = {};
for (const key in obj) {
if (key === oldKey) {
result[newKey] = obj[key];
} else {
result[key] = obj[key];
}
}
return result;
}
const user = { id: 1, name: "Alice", password: "secret", role: "admin" };
console.log(pick(user, ["id", "name"]));
// { id: 1, name: "Alice" }
console.log(omit(user, ["password"]));
// { id: 1, name: "Alice", role: "admin" }
console.log(renameKey(user, "name", "fullName"));
// { id: 1, fullName: "Alice", password: "secret", role: "admin" }Giải thích:
pick: Chỉ copy các key được chỉ định. Dùngfor...oftrênkeys.omit: DùngSetđể kiểm tra nhanh (O(1)).for...induyệt tất cả enumerable keys.keySet.has(key)kiểm tra key có trong danh sách loại bỏ không.renameKey: Duyệt tất cả keys. Khi gặpoldKey, tạonewKeythay thế. Các key khác copy nguyên.- Cả ba function đều tạo object mới, không mutate
objgốc.
9. Edge Cases (Các trường hợp ngoại lệ)
Dot notation với key từ biến
const field = "email";
const user = { email: "a@b.com" };
console.log(user.field); // undefined
console.log(user[field]); // "a@b.com"Tại sao: user.field tìm property tên "field". user[field] evaluate field thành "email".
Cách nhận biết: Property chắc chắn tồn tại nhưng truy cập ra undefined.
Cách xử lý: Dùng bracket notation khi key đến từ biến.
Key là số hoặc chứa ký tự đặc biệt
const obj = { "123": "numeric", "full-name": "kebab" };
console.log(obj.123); // ❌ SyntaxError
console.log(obj["123"]); // ✅ "numeric"
console.log(obj.full-name); // ❌ NaN (hiểu là obj.full trừ name)
console.log(obj["full-name"]); // ✅ "kebab"Tại sao: Dot notation yêu cầu identifier hợp lệ (không bắt đầu bằng số, không chứa dấu gạch ngang, dấu cách).
Cách nhận biết: SyntaxError hoặc kết quả toán học bất ngờ.
Cách xử lý: Luôn dùng bracket notation cho key không phải identifier.
delete không xóa reference của nested object
const parent = { child: { name: "A" } };
const ref = parent.child;
delete parent.child;
console.log(ref); // { name: "A" } — object vẫn tồn tạiTại sao: delete xóa property "child" trên parent (xóa reference từ parent), nhưng object { name: "A" } vẫn được ref giữ lại. delete không phải là "hủy object".
Cách nhận biết: Object vẫn reachable qua biến khác sau khi delete.
Cách xử lý: Nếu cần object không còn reachable, đảm bảo xóa tất cả reference (bao gồm ref).
undefined value vs missing property
const obj = { a: undefined };
console.log(obj.a); // undefined
console.log(obj.b); // undefined
console.log("a" in obj); // true
console.log("b" in obj); // falseTại sao: in kiểm tra sự tồn tại của property, không quan tâm value. Truy cập property không tồn tại cũng trả về undefined.
Cách nhận biết: Không thể phân biệt "thiếu" và "có nhưng undefined" bằng obj.x === undefined.
Cách xử lý: Dùng "x" in obj hoặc Object.hasOwn(obj, "x") (ES2022) khi cần phân biệt.
Nested object mutation qua reference
const config = { theme: { color: "blue" } };
const theme = config.theme;
theme.color = "red";
console.log(config.theme.color); // "red"Tại sao: theme và config.theme cùng trỏ đến một object. Mutation qua một reference ảnh hưởng tất cả.
Cách nhận biết: Object "tự đổi" sau khi gán cho biến khác.
Cách xử lý: Tạo shallow copy nếu cần isolate: const theme = { ...config.theme }. 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 sanitizeUser được gọi để loại bỏ password trước khi gửi response, nhưng user gốc trong database cũng mất password.
function sanitizeUser(user) {
delete user.password;
return user;
}
const dbUser = {
id: 1,
name: "Alice",
password: "secret123",
profile: { age: 24 }
};
const response = sanitizeUser(dbUser);
console.log(response);
// { id: 1, name: "Alice", profile: { age: 24 } } ✅
console.log(dbUser.password);
// undefined ❌ — database object bị sửa!Reproduction (Tái hiện lỗi): Chạy code trên.
Evidence (Bằng chứng):
dbUser.passwordthànhundefinedsau khi gọisanitizeUser.response === dbUser(cùng reference).
Hypothesis (Giả thuyết): sanitizeUser nhận user (reference đến object gốc). delete user.password xóa property trực tiếp trên object gốc. Return cùng object.
Verification (Xác minh):
console.log(response === dbUser); // trueRoot Cause (Nguyên nhân gốc rễ): Function tưởng chừng "tạo bản sao sạch" nhưng thực chất mutate input. delete là mutating operation. Đây là lỗi security nghiêm trọng: API response handler làm mất dữ liệu database.
Fix (Sửa):
function sanitizeUser(user) {
const result = {};
for (const key in user) {
if (key !== "password") {
result[key] = user[key];
}
}
return result;
}Hoặc dùng computed property logic:
function sanitizeUser(user) {
const { password, ...rest } = user; // destructuring — preview Stage 0.6.7
return rest;
}Prevention (Phòng ngừa):
- Nghi ngờ mọi function tên
sanitize,format,cleanmà không tạo object mới. deletechỉ dùng khi bạn cố ý mutate object (ví dụ: xóa key trong object tạm).- Trong code review, kiểm tra: function có tạo object mới không? Nếu không, nó đang mutate input.
11. Design Exercise (Bài tập thiết kế giải pháp)
Bạn cần lưu cấu hình theme cho từng user. Mỗi user có color và fontSize.
const user = {
name: "Alice",
themeColor: "blue",
themeFontSize: 14
};const user = {
name: "Alice",
theme: {
color: "blue",
fontSize: 14
}
};Câu hỏi:
- Option A dễ truy cập hơn ở điểm nào?
- Option B có lợi gì khi cần truyền theme sang component khác?
- Nếu cần reset theme về default, option nào dễ hơn? Tại sao?
[Đáp án tham khảo]
Bạn nghĩ:
- Option A:
user.themeColorngắn gọn, không cần nested access. Không louser.themelàundefined. - Option B: Có thể truyền
user.themenhư một unit sang component khác. Component chỉ cần biết vềtheme, không cần biết toàn bộuser. Dễ validate (kiểm tra objectthemecó đủ field). - Option B dễ reset hơn:
user.theme = defaultTheme(thay cả object). Option A phải reset từng field:user.themeColor = defaultColor; user.themeFontSize = defaultSize. Option B cũng dễpick/omithơn.
- Option A:
Kết luận: Nested object tốt cho grouping logic và data ownership. Flat object tốt cho simplicity. Trong production, nested thường preferred khi data có domain riêng (theme, profile, settings).
12. Production Scenario (Tình huống thực tế)
Context: API trả về danh sách sản phẩm với key là ID:
const apiResponse = {
"p-101": { name: "Phone", price: 500 },
"p-102": { name: "Laptop", price: 1200 }
};Frontend cần hiển thị giá đã format. Developer viết:
function formatPrices(response) {
for (const id in response) {
response[id].price = "$" + response[id].price;
}
return response;
}Symptom: Sau khi format để hiển thị, chức năng "sắp xếp theo giá" bị hỏi — giá trở thành string "$500" không thể so sánh số học. Hoặc khi gửi lại API để cập nhật, server reject vì price phải là number.
Constraint: apiResponse được cache để dùng ở nhiều chỗ.
Câu hỏi:
- Tại sao giá bị "nhiễm" string mãi mãi?
formatPriceslà pure hay impure? Tại sao?- Viết lại đúng — trả về object mới với giá đã format nhưng không sửa gốc.
[Đáp án tham khảo]
Bạn nghĩ:
formatPricesmutateresponse[id]object gốc.pricebị ghi đè thành string. Cache bị nhiễm.- Impure. Mutate input argument. Có side effect observable bên ngoài.
- js(Ở 0.6.8 sẽ học object spread
function formatPrices(response) { const result = {}; for (const id in response) { const item = response[id]; result[id] = { name: item.name, price: item.price, displayPrice: "$" + item.price }; } return result; }{ ...item }để copy generic mà không cần liệt kê từng property.)
Bài học: Mutate API response cache = poison downstream. Luôn tạo view model cho display layer.
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 có bug khi truy cập object với key động. Dự đoán output.js
const key = "name"; const user = { name: "Alice" }; console.log(user.key); console.log(user[key]); - Hỏi AI: "Tại sao
user.keyraundefinedtrong khiusercó propertyname?" - So sánh câu trả lời AI với nhận định của bạn. AI có giải thích rõ sự khác biệt giữa identifier và expression trong dot notation không?
- Verify bằng MDN: tìm "Property accessors" trên MDN và đọc phần "Dot notation" vs "Bracket notation".
Gợi ý
AI thường biết câu trả lời đúng nhưng đôi khi giải thích mơ hồ bằng cách nói "dot notation không hỗ trợ biến". Nếu AI không chỉ ra rằng dot notation sau dấu chấm là identifier, không phải expression — nghĩa là JavaScript parser đọc key như chuỗi literal "key", không evaluate biến — bạn đã tìm ra điểm mù. Đây là distinction cốt lõi của language syntax.
Đáp án tham khảo
Bạn nghĩ:
user.keytìm property có tên"key".keysau dấu chấm là identifier, không phải biến được evaluate.user[key]evaluate biểu thứckeytrước (thành"name"), rồi dùng kết quả làm property name.AI trả lời (typical):
user.keylooks for a property literally named"key".user[key]evaluates the variablekeyfirst.- Since
keyis"name", it looks for the"name"property.
So sánh: AI thường đúng về behavior nhưng đôi khi không giải thích tại sao JavaScript thiết kế như vậy: dot notation là syntax sugar cho bracket notation với string literal.
obj.nametương đươngobj["name"]. Parser không evaluatenamenhư biến vì đó là luật của dot notation.Điểm AI nói sai hoặc quá mơ hồ: "Dot notation doesn't support variables" — đúng nhưng chưa đủ. AI hiếm khi nói rõ: "Đây là quyết định syntax của language, không phải limitation. Dot notation cho phép minification và static analysis; bracket notation cho phép dynamic access. Hai cái có mục đích khác nhau."
Kết luận: Nếu bạn chỉ ra được rằng dot notation = static key (biết lúc viết code), bracket notation = dynamic key (biết lúc chạy), bạn đã hiểu sâu hơn AI. Concept này sẽ quay lại ở Stage 2 (Computed property names, Proxy), Stage 6 (TypeScript indexed access types), và Stage 8 (React dynamic component keys).
14. Teach Back (Dạy lại)
Giả sử một junior developer hỏi bạn:
"Em thấy
obj.namevàobj["name"]đều ra cùng kết quả. Vậy tại sao người ta không chỉ dùng dot notation cho mọi thứ? Và tại sao đôi khi em thấy code dùng dấu ngoặc vuông với cả số nhưarr[0]? Array và object khác nhau ở đâu?"
Hãy giải thích trong 2 phút, dùng đúng terminology: identifier, expression, key, index.
Mô phỏng
- Bạn nói:
obj.namechỉ hoạt động khinamelà tên hợp lệ — không số, không dấu gạch ngang, không dấu cách, không từ biến. JavaScript đọcnamenhư một identifier, không phải giá trị của biến.
obj["name"] hoạt động với mọi thứ vì bên trong ngoặc là expression — có thể là string literal, biến, hoặc tính toán. obj[key] với key = "name" sẽ evaluate key trước, thành "name", rồi truy cập.
Về arr[0]: array là object đặc biệt. Key của nó là số dạng string ("0", "1"). arr[0] thực ra là arr["0"]. Array thêm tính năng length và một số method riêng, nhưng về bản chất cũng là object có key.
Kết luận: Dùng dot khi bạn viết tên key trong code. Dùng bracket khi key đến từ biến, tính toán, hoặc không phải identifier hợp lệ.
💡 Tưởng tượng dot notation như tìm ngăn kéo theo nhãn dán sẵn trên tủ — bạn phải đọc được nhãn. Bracket notation như tra số phòng từ biến — bạn có thể tính toán ra số phòng rồi mới đi tìm.
Gợi ý đánh giá bản thân
- Đồng nghiệp có hiểu tại sao
obj[key]khácobj.keykhikeylà biến không? - Bạn có tránh được việc nói "dấu ngoặc vuông dùng cho array, dấu chấm dùng cho object" không? (Cả hai đều dùng được cho cả hai, nhưng dot notation hạn chế hơn.)
- Nếu đồng nghiệp hỏi: "Vậy
obj.123sai ở đâu?" — bạn trả lời được không? (Gợi ý:123không phải identifier hợp lệ, parser không hiểu.) - Nếu đồng nghiệp hỏi: "Tại sao không dùng
deleteđể sanitize object trước khi gửi API?" — bạn trả lời được trong 10 giây không? (Gợi ý:deletemutate object gốc. Phải tạo object mới.)
15. Assessment (Đánh giá)
| Objective (Mục tiêu) | Hình thức đánh giá | Task |
|---|---|---|
| Create and access object (Tạo và truy cập object) | Implementation (Thực hành) | Implementation Lab Level 1 |
| Distinguish dot vs bracket (Phân biệt dot/bracket) | Prediction (Dự đoán) | Prediction Câu 1, 4 |
| Use computed property name (Dùng computed property) | Implementation (Thực hành) | Implementation Lab Level 1 |
| Handle nested access safely (Truy cập nested an toàn) | Implementation (Thực hành) | Implementation Lab Level 2 |
| Avoid mutating input object (Tránh mutate input) | Debug Lab | Debug Lab Section |
| Pick/omit/rename keys | Implementation (Thực hành) | Implementation Lab Level 3 |
| Choose flat vs nested structure | Design Decision | Design Exercise |
| Explain identifier vs expression | Teach Back | Teach Back Section |
16. Exit Criteria (Tiêu chí qua bài)
- [ ] Có thể tạo object bằng literal với key cố định và computed property name.
- [ ] Có thể phân biệt dot notation (static key, identifier) và bracket notation (dynamic key, expression).
- [ ] Có thể dự đoán output khi dùng dot notation với biến (luôn tìm key bằng tên biến, không phải giá trị).
- [ ] Có thể thêm, sửa, xóa property bằng cả hai notation.
- [ ] Có thể nhận diện sự khác biệt giữa property missing và property có giá trị
undefined(dùngin). - [ ] Có thể debug lỗi mutate object gốc qua
deletehoặc nested reference assignment. - [ ] Có thể viết function
pick,omit,renameKeytạo object mới không mutate input. - [ ] Có thể viết
safeGetđể truy cập nested property không bị crash.
17. Spiral Connection (Liên kết xoắn ốc)
Previous (Trước): Reduce (0.6.4) — bạn đã biết gom array thành object bằng
reduce. Giờ bạn học cấu trúc object đó hoạt động như thế nào.
Current (Hiện tại): Objects — creation, dot vs bracket access, computed property, add/update/delete, nested reference,
inoperator. Đây là nền tảng cho mọi data structure phức tạp.
Next (Tiếp theo):
- 0.6.6 (Object Utilities) —
Object.keys,Object.values,Object.entries: biến object thành array để dùngmap/filter/reduce.- 0.6.7 (Destructuring) — Trích xuất nhiều property cùng lúc từ object/array.
- 0.6.8 (Spread & Shallow Copy) —
{ ...obj }tạo object mới. Tại sao chỉ shallow copy? Nested object vẫn shared reference.- Stage 1 (Execution Model) — Object là reference type. Khi pass object vào function, bạn pass reference. Mutation behavior được giải thích bởi execution model.
- Stage 2 (Object Model) — Prototype chain: tại sao
obj.toString()hoạt động dù bạn không định nghĩatoString. Property descriptor vàObject.defineProperty.- Stage 8 (React) — Props là object. Destructuring props là pattern bắt buộc. State object phải immutable — tạo mới bằng spread.