Skip to content

Lesson 0.6.5 — Objects ​

Bài 0.6.5 — Objects
Property, Access, Computed key & Nested objects • 30 phút
0:00 / 0:00

0. Metadata ​

FieldValue
Stage0 — JavaScript Language Foundation
Module0.6 — Data Structures
Lesson0.6.5
CompetencyC01.6 — Arrays / Objects
Depth TargetL2–L3
PrerequisitesReduce (0.6.4), Arrays (0.6.2), Primitive vs Object (0.2.1/0.2.4), Variables & Bindings (0.2.2)
Estimated Cognitive LoadMedium

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:

js
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 const cấ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ể:

  1. Tạo object bằng literal và truy cập property bằng dot notation và bracket notation.
  2. Phân biệt dot notation (key cố định, identifier) và bracket notation (key động, expression).
  3. Sử dụng computed property name khi tạo object với key động.
  4. Thêm, sửa, xóa property trên object và nhận diện hậu quả của delete.
  5. Nhận diện nested object reference trap (mutate nested object ảnh hưởng object gốc).
  6. 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).
  • delete xó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 Notationobj.name — truy cập property có key là identifier hợp lệ.
Bracket Notationobj["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/Updateobj.newKey = value hoặc obj["newKey"] = value.
deletedelete 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 ObjectObject 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 missingobj.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 orderString 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) ​

ConceptLý do chưa đào sâu
hasOwnPropertyKiểm tra property thuộc object (không phải prototype). Thuộc Stage 2 (Prototype).
Object.definePropertyTạo property với descriptor (writable, enumerable, configurable). Thuộc Stage 2.
Symbol keysSymbol 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.
  • this trong 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.

js
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:

  1. Biến thành object { API_URL: "...", API_TIMEOUT: "...", FEATURE_FLAG_X: "..." }.
  2. Không mutate array gốc (đã học ở 0.6.2–0.6.4).
  3. Hỗ trợ key động (từ biến key trong array).
js
function buildConfig(vars) {
  const config = {};
  for (const v of vars) {
    config.v.key = v.value; // ❌ config.v là undefined
  }
  return config;
}
js
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):

js
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 ​

js
const key = "name";
const user = { name: "Alice" };
console.log(user.key);
console.log(user[key]);

Câu 2 ​

js
const obj = { a: 1, b: 2 };
delete obj.a;
console.log(obj.a);
console.log("a" in obj);

Câu 3 ​

js
const user = {
  profile: { age: 24 }
};
const p = user.profile;
p.age = 30;
console.log(user.profile.age);

Câu 4 ​

js
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 ​

js
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.key tìm property tên "key" — không tồn tại → undefined. user[key] evaluate key thành "name" → tìm property "name" → "Alice".

Câu 2: undefined và false — delete

  • Giải thích: delete obj.a xóa property "a". obj.a truy cập property không tồn tại → undefined. "a" in obj kiểm tra sự tồn tại → false. Khác với obj.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.profile copy reference đến object nested. p.age = 30 mutate object đó. user.profile vẫ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ới key = "123" tạo property "123". obj["123"] truy cập property "123" → "bracket". obj.123 là SyntaxError (dot notation không hợp lệ với số), nhưng obj["123"] hợp lệ.

Câu 5: undefined, true, undefined, false — in vs undefined

  • Giải thích: settings.theme là undefined (property tồn tại, giá trị là undefined). "theme" in settings là true (property tồn tại). settings.font là undefined (property không tồn tại). "font" in settings là 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.

js
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]
js
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.
  • reduce tích lũy object từ array.
  • acc bắ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.

js
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]
js
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"]));
// undefined

Giải thích:

  • current bắt đầu là obj.
  • Vòng lặp duyệt từng key trong path.
  • current === null || current === undefined ngăn lỗi Cannot read properties of undefined.
  • current[key] dùng bracket notation vì key là 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):

js
// 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 qua keys, lấy obj[key] cho mỗi key.
  • omit: Dùng for...in để duyệt tất cả keys, bỏ qua key trong danh sách loại bỏ. Dùng Set (đã gặp ở 0.6.3) để kiểm tra nhanh.
  • renameKey: Dùng for...in để duyệt keys. Khi gặp oldKey, gán giá trị cho newKey thay thế. Các key khác copy nguyên.
[Đáp án tham khảo]
js
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ùng for...of trên keys.
  • omit: Dùng Set để kiểm tra nhanh (O(1)). for...in duyệ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ặp oldKey, tạo newKey thay thế. Các key khác copy nguyên.
  • Cả ba function đều tạo object mới, không mutate obj gốc.

9. Edge Cases (Các trường hợp ngoại lệ) ​

Dot notation với key từ biến

js
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

js
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

js
const parent = { child: { name: "A" } };
const ref = parent.child;
delete parent.child;
console.log(ref); // { name: "A" } — object vẫn tồn tại

Tạ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

js
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);   // false

Tạ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

js
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.

js
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.password thành undefined sau khi gọi sanitizeUser.
  • 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):

js
console.log(response === dbUser); // true

Root 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):

js
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:

js
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, clean mà không tạo object mới.
  • delete chỉ 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.

js
const user = {
  name: "Alice",
  themeColor: "blue",
  themeFontSize: 14
};
js
const user = {
  name: "Alice",
  theme: {
    color: "blue",
    fontSize: 14
  }
};

Câu hỏi:

  1. Option A dễ truy cập hơn ở điểm nào?
  2. Option B có lợi gì khi cần truyền theme sang component khác?
  3. 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ĩ:

    1. Option A: user.themeColor ngắn gọn, không cần nested access. Không lo user.theme là undefined.
    2. Option B: Có thể truyền user.theme như 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 object theme có đủ field).
    3. 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/omit hơn.
  • 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:

js
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:

js
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:

  1. Tại sao giá bị "nhiễm" string mãi mãi?
  2. formatPrices là pure hay impure? Tại sao?
  3. 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ĩ:

    1. formatPrices mutate response[id] object gốc. price bị ghi đè thành string. Cache bị nhiễm.
    2. Impure. Mutate input argument. Có side effect observable bên ngoài.
    3. js
      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;
      }
      (Ở 0.6.8 sẽ học object spread { ...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

  1. 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]);
  2. Hỏi AI: "Tại sao user.key ra undefined trong khi user có property name?"
  3. 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?
  4. 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.key tìm property có tên "key". key sau dấu chấm là identifier, không phải biến được evaluate. user[key] evaluate biểu thức key trước (thành "name"), rồi dùng kết quả làm property name.

  • AI trả lời (typical):

    • user.key looks for a property literally named "key".
    • user[key] evaluates the variable key first.
    • Since key is "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.name tương đương obj["name"]. Parser không evaluate name như 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.name và 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.name chỉ hoạt động khi name là 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 đọc name như 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ác obj.key khi key là 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.123 sai ở đâu?" — bạn trả lời được không? (Gợi ý: 123 khô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 ý: delete mutate 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 LabDebug Lab Section
Pick/omit/rename keysImplementation (Thực hành)Implementation Lab Level 3
Choose flat vs nested structureDesign DecisionDesign Exercise
Explain identifier vs expressionTeach BackTeach 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ùng in).
  • [ ] Có thể debug lỗi mutate object gốc qua delete hoặc nested reference assignment.
  • [ ] Có thể viết function pick, omit, renameKey tạ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, in operator. Đâ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ùng map/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ĩa toString. 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.
📴 Offline Mode — Content served from cache