Skip to content

Lesson 0.2.4 — Object Values ​

Bài 0.2.4 — Object Values
Object, Array, Function, Date, Map, Set • 28 phút
0:00 / 0:00

0. Metadata ​

FieldValue
Stage0 — JavaScript Language Foundation
Module0.2 — Values, Variables & Types
Lesson0.2.4
CompetencyC01.1 — Values & Types
DepthL2–L3 (Explain → Use)
PrerequisitesLesson 0.2.1 — Values; Lesson 0.2.2 — Variables & Bindings; Lesson 0.2.3 — Primitive Types
Cognitive LoadMedium

1. Why This Exists (Vì sao cần học) ​

Bạn viết:

js
const a = { name: "Alice" };
const b = { name: "Alice" };
console.log(a === b);

Kết quả: false.

Bạn viết tiếp:

js
const c = a;
c.name = "Bob";
console.log(a.name);

Kết quả: "Bob".

Cùng là object, cùng nội dung — một cái không bằng nhau, một cái lại "lan" thay đổi sang biến khác. Nếu bạn mang mental model từ primitive sang object, bạn sẽ bị sốc.

Lesson này xây dựng mental model về object identity và reference — nền tảng để hiểu mọi thứ từ React state đến memory leak ở các Stage sau.

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 (Lesson 0.2.1).
  • Hiểu variable là binding (Lesson 0.2.2).
  • Biết 7 primitive types và behavior của typeof (Lesson 0.2.3).

3. Learning Objectives (Mục tiêu học tập) ​

Sau bài này, bạn có thể:

  1. Giải thích tại sao hai object có cùng nội dung lại không bằng nhau (===).
  2. Dự đoán khi nào mutation trên một variable ảnh hưởng variable khác.
  3. Phân biệt rebind (gán object mới) và mutation (sửa bên trong object).
  4. Sử dụng spread ({...obj}, [...arr]) để tạo shallow copy đúng lúc.
  5. Biết sự tồn tại của các built-in object types như Date, RegExp, Map, Set, và nhận diện Array cùng Function là object types.

4. Mental Model (Mô hình tư duy) ​

Mental Model — Object là Ngôi nhà, Variable là Địa chỉ

Primitive giống như tiền mặt: bạn đưa tôi 100k, tôi cầm 100k riêng, tiền của bạn không đổi.

Object giống như ngôi nhà: bạn đưa tôi địa chỉ nhà (reference). Tôi đến nhà đó, sơn lại tường. Bạn quay lại nhà của bạn — tường đã đổi màu.

Hai binding a và b cùng tham chiếu một object; khi b.x thay đổi từ 1 thành 2, cả a và b đều thấy giá trị mới.

Khi bạn dùng === với object, bạn không so sánh nội dung ngôi nhà. Bạn so sánh địa chỉ nhà.

js
{ x: 1 } === { x: 1 }   // false
// Hai ngôi nhà khác nhau, dù nội thất giống nhau

Code Review Lens

Khi thấy const b = a; với object, hãy tự hỏi: "Dòng code tiếp theo có mutate b không? Nếu có, a có bị ảnh hưởng không?"

5. Core Concepts (Các khái niệm cốt lõi) ​

Essential (Bắt buộc) ​

Khái niệmÝ nghĩa
Object valueGiá trị có cấu trúc, có identity riêng.
ReferenceBinding trỏ đến một vị trí bộ nhớ chứa object.
IdentityObject được nhận diện bằng địa chỉ, không phải bằng nội dung.
MutationThay đổi nội dung bên trong object mà không đổi identity.
Shallow copyTạo object mới, copy property cấp 1. Spread {...a} hoặc [...a].
ArrayOrdered collection, là một loại object.
FunctionCallable object, cũng là object.

Supporting (Hỗ trợ) ​

  • Date, RegExp, Map, Set là built-in object types.
  • typeof [] trả về "object" (array là object).
  • typeof function(){} trả về "function" (special behavior, nhưng function vẫn là object).

Awareness (Biết tồn tại) ​

  • Map khác plain object ở chỗ key có thể là bất kỳ type nào, và giữ thứ tự insertion.
  • Set chỉ chứa unique values.
  • Date và RegExp là object wrappers cho giá trị thời gian và pattern.

Out of Scope (Không học trong bài này) ​

  • Prototype chain: __proto__, Object.prototype. Stage 2.
  • V8 hidden classes: Shape, inline cache. Stage 11.
  • Deep clone: structuredClone, recursive copy. Stage 2.
  • WeakMap / WeakSet: Garbage collection relationship. Stage 11.

6. Worked Example (Ví dụ phân tích từng bước) ​

Xét đoạn code sau:

js
const user = { name: "A", prefs: { theme: "dark" } };
const copy = user;

copy.name = "B";

const clone = { ...user };
clone.name = "C";

console.log(user.name);
console.log(copy.name);
console.log(clone.name);
console.log(user.prefs === clone.prefs);

Step 1 — Declaration + Shared Reference

js
const user = { name: "A", prefs: { theme: "dark" } };
const copy = user;

Engine tạo một object. Cả user và copy đều bind đến cùng một reference.

Step 2 — Mutation

js
copy.name = "B";

Engine tìm object mà copy trỏ đến, sửa property name. Vì user trỏ cùng object, user.name cũng thành "B".

Step 3 — Shallow Copy

js
const clone = { ...user };

Engine tạo một object mới. Copy các property cấp 1 từ user sang. Nhưng prefs là một object — shallow copy chỉ copy reference của prefs, không tạo prefs mới.

Step 4 — Mutation trên clone

js
clone.name = "C";

Engine sửa property name trên object mới (clone). Object gốc (user) không bị ảnh hưởng.

Step 5 — Identity check

js
console.log(user.prefs === clone.prefs); // true

Vì shallow copy chỉ copy reference, user.prefs và clone.prefs vẫn trỏ đến cùng một object.

Step 6 — Output

js
console.log(user.name);   // "B"
console.log(copy.name);   // "B"
console.log(clone.name);  // "C"

7. Prediction Exercise (Bài tập dự đoán) ​

Đừng chạy code. Dự đoán output và giải thích bằng mental model "ngôi nhà và địa chỉ".

P1

js
const a = [1, 2];
const b = a;
b.push(3);
console.log(a.length);

P2

js
const x = { count: 0 };
const y = { count: 0 };
console.log(x === y);

P3

js
const base = { items: ["a"] };
const copy = { ...base };
copy.items.push("b");
console.log(base.items.length);

P4

js
const fn1 = () => {};
const fn2 = fn1;
console.log(fn1 === fn2);

P5

js
const config = { debug: true };
config = { debug: false };

P6

js
const items = [{ id: 1 }];
const copy = [...items];
copy[0].id = 2;
console.log(items[0].id);
[Đáp án & Giải thích]

P1

  • Output: 3
  • Why: a và b cùng trỏ đến một array. push là mutation. Cả hai đều thấy thay đổi.

P2

  • Output: false
  • Why: x và y là hai object khác nhau (hai ngôi nhà khác nhau). === so sánh identity (địa chỉ), không phải nội dung.

P3

  • Output: 2
  • Why: { ...base } tạo object mới ở cấp 1, nhưng items vẫn là cùng một array reference. copy.items.push("b") mutate array gốc.

P4

  • Output: true
  • Why: fn2 được gán reference của fn1. Cả hai trỏ đến cùng một function object.

P5

  • Output: TypeError: Assignment to constant variable.
  • Why: Dòng config = { debug: false } cố gắng rebind const. Mutation (config.debug = false) thì được, nhưng rebind thì không.

P6

  • Output: 2
  • Why: Spread array tạo array mới nhưng element vẫn là cùng object reference. Đây là trường hợp "shallow copy của container chứa object" — rất phổ biến trong React state update.

8. Implementation Lab (Bài lab thực hành) ​

Lab A — Guided (Có hướng dẫn) ​

Tạo một object settings, sau đó tạo backup trỏ đến cùng object. Thay đổi backup.theme và kiểm chứng settings.theme cũng đổi. Sau đó tạo snapshot bằng shallow copy và thay đổi snapshot.theme — kiểm chứng settings.theme không đổi.

js
const settings = { theme: "dark", lang: "vi" };

// TODO: tạo backup trỏ cùng reference
// TODO: đổi backup.theme
// TODO: log settings.theme

// TODO: tạo snapshot bằng shallow copy
// TODO: đổi snapshot.theme
// TODO: log settings.theme
[Đáp án tham khảo]
js
const settings = { theme: "dark", lang: "vi" };

const backup = settings;
backup.theme = "light";
console.log(settings.theme); // "light" (cùng object)

const snapshot = { ...settings };
snapshot.theme = "auto";
console.log(settings.theme); // "light" — vẫn là giá trị sau khi backup mutate; snapshot không ảnh hưởng thêm

Lab B — Partial Scaffold (Khung sườn) ​

Hoàn thành hàm updateUser sao cho:

  • Nhận user object và updates object.
  • Trả về một object mới chứa merge của user và updates.
  • Không được mutate user gốc.
js
function updateUser(user, updates) {
  // TODO: tạo object mới, merge user và updates
  // implement here
}

const user = { name: "A", role: "user" };
const updated = updateUser(user, { role: "admin" });

console.log(user.role);    // phải là "user"
console.log(updated.role); // phải là "admin"
[Đáp án tham khảo]
js
function updateUser(user, updates) {
  return { ...user, ...updates };
}

Lab C — Independent (Tự làm) ​

Viết hàm addTag nhận một post object và một tag string. Hàm trả về post mới với tag được thêm vào tags array, nhưng không được mutate post gốc (kể cả array tags bên trong).

js
const post = { id: 1, tags: ["js"] };
const newPost = addTag(post, "react");

console.log(post.tags);    // ["js"]
console.log(newPost.tags); // ["js", "react"]
[Đáp án tham khảo]
js
function addTag(post, tag) {
  return {
    ...post,
    tags: [...post.tags, tag]
  };
}

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

Shallow Copy không bảo vệ Nested Object

js
const a = { nested: { value: 1 } };
const b = { ...a };
b.nested.value = 2;

console.log(a.nested.value); // 2

Shallow copy chỉ tạo object mới ở cấp 1. Các property là object vẫn share reference. Đây là một trong những bug "immutability" phổ biến nhất.

Array là Object

js
const arr = [1, 2];
arr.prop = "hello";
console.log(arr.prop); // "hello"

Array là object, nên có thể thêm property tùy ý. Điều này hiếm dùng, nhưng chứng minh array thực chất là object có đặc tính đặc biệt (ordered, .length).

Function là Object

js
const fn = () => {};
fn.meta = "info";
console.log(fn.meta); // "info"

Function là callable object. Chúng có thể mang property. Điều này được dùng trong một số pattern (ví dụ: React component với .displayName), nhưng ở Stage 0 chỉ cần biết function thuộc category object.

typeof null lại gây rối

js
const date = new Date();
console.log(typeof date); // "object"

const map = new Map();
console.log(typeof map);  // "object"

Tất cả object types đều trả về "object" (trừ function). Để phân biệt Date, Array, RegExp, cần dùng Array.isArray() hoặc instanceof — sẽ học ở Stage 2.

10. Debug Lab (Bài lab gỡ lỗi) ​

Symptom (Triệu chứng): Hàm createDefaultUser trả về user mới, nhưng sau vài lần gọi, tất cả users đều có cùng một permissions array.

Reproduction (Tái hiện lỗi):

js
const DEFAULT_USER = {
  name: "Guest",
  permissions: ["read"]
};

function createDefaultUser() {
  return DEFAULT_USER;
}

const u1 = createDefaultUser();
u1.permissions.push("write");

const u2 = createDefaultUser();
console.log(u2.permissions); // ["read", "write"] ??? Tôi đâu có thêm!

Evidence (Bằng chứng):

  • u2.permissions chứa "write" dù không có code nào sửa u2 trực tiếp.
  • u1 === u2 trả về true.

Hypothesis (Giả thuyết):createDefaultUser trả về cùng một object reference. u1 và u2 trỏ đến cùng object. Khi u1 mutate permissions, u2 nhìn thấy.

Verification (Xác minh):

js
console.log(u1 === DEFAULT_USER); // true
console.log(u2 === DEFAULT_USER); // true

Cả hai đều là cùng một object.

Root Cause (Nguyên nhân gốc rễ): Trả về reference thay vì tạo object mới. permissions là array bên trong object — mutation lan từ u1 sang u2 vì share reference.

Fix (Sửa):

js
function createDefaultUser() {
  return {
    name: "Guest",
    permissions: ["read"]
  };
}

Hoặc nếu cần dùng DEFAULT_USER làm template:

js
function createDefaultUser() {
  return {
    ...DEFAULT_USER,
    permissions: [...DEFAULT_USER.permissions]
  };
}

Prevention (Phòng ngừa):

  • Factory function phải trả về object mới, không trả về shared reference.
  • Nếu object có nested object/array, shallow copy ({...obj}) không đủ — cần copy nested structure.
  • Đặt tên hằng số dùng chung bằng UPPER_SNAKE_CASE để báo hiệu "không nên mutate trực tiếp".

11. Design Exercise (Bài tập thiết kế giải pháp) ​

Context

Bạn đang viết một hàm mergeState(current, updates). current là state object lớn. updates chỉ chứa các field cần thay đổi.

Yêu cầu:

  1. Không được mutate current.
  2. Không được mất dữ liệu trong current mà updates không đề cập.
  3. updates có thể chứa nested object (ví dụ: { user: { name: "B" } }).

Câu hỏi: Với kiến thức hiện tại (chỉ shallow copy), bạn sẽ implement như thế nào? Và tại sao shallow copy có thể chưa đủ với nested object?

[Đáp án tham khảo]

Với kiến thức hiện tại (shallow copy):

js
function mergeState(current, updates) {
  return { ...current, ...updates };
}

Vấn đề với nested object:

js
const state = { user: { name: "A", age: 20 } };
const newState = mergeState(state, { user: { name: "B" } });

console.log(newState.user.age); // undefined

Vì ...updates ghi đè property user trong object mới, thay thế cả object { name: "A", age: 20 } bằng { name: "B" }, nên mất age. Shallow copy không merge nested — nó replace nested.

Nhận xét: Đây là lý do tại sao production cần deep merge hoặc immutable update patterns. Bạn sẽ học cách xử lý đúng ở Stage 2 (Object Model) và Stage 8 (React State).

12. Production Scenario (Tình huống thực tế) ​

Một team frontend lưu cache API response:

js
const cache = {};

function fetchUser(id) {
  if (cache[id]) return cache[id];
  const data = { id, name: "User " + id, settings: { theme: "dark" } };
  cache[id] = data;
  return data;
}

Một component sau đó làm:

js
const user = fetchUser(1);
user.settings.theme = "light"; // Cập nhật theo user preference

Tuần sau, một component khác gọi fetchUser(1) và nhận theme: "light" — dù API chưa bao giờ trả về "light". Cache đã bị "nhiễm" bởi mutation.

Câu hỏi:

  1. Tại sao cache bị nhiễm?
  2. Sửa như thế nào để component vẫn có thể cập nhật settings mà không ảnh hưởng cache?
[Đáp án tham khảo]
  1. fetchUser trả về reference trực tiếp từ cache. Component mutate user.settings — điều này mutate object trong cache.

  2. Fix: Trả về shallow copy từ cache, hoặc component tự tạo local copy trước khi mutate.

js
function fetchUser(id) {
  if (cache[id]) return { ...cache[id], settings: { ...cache[id].settings } };
  // ... fetch và lưu cache
}

Hoặc component tự quản lý:

js
const user = fetchUser(1);
const localUser = { ...user, settings: { ...user.settings } };
localUser.settings.theme = "light";

13. AI-assisted Exercise (Bài tập với AI) ​

Level B — Challenge (Thách thức AI)

  1. Tự trả lời trước: Viết ra giấy: tại sao [] === [] là false? Dùng analogy "ngôi nhà/địa chỉ".
  2. Hỏi AI: "Why does [] === [] return false in JavaScript?"
  3. So sánh: AI có dùng analogy "reference/address" không? Hay AI chỉ nói "because arrays are objects and compared by reference"?
  4. Verify: Kiểm tra lại bằng MDN page "Equality comparisons and sameness". Xem documentation dùng từ "reference" hay "identity"?

Gợi ý

Hãy để ý xem AI có giải thích tại sao reference comparison tồn tại (vì object có thể lớn, so sánh nội dung sẽ rất chậm) hay không. Nhiều AI chỉ dừng lại ở "rule" mà không giải thích engineering reason đằng sau.

Đáp án tham khảo
  • Bạn nghĩ

    • [] === [] là false vì mỗi [] tạo ra một array object mới. Chúng ở hai địa chỉ bộ nhớ khác nhau.
    • === so sánh identity (địa chỉ), không phải nội dung.
    • Lý do engine làm vậy: object có thể rất lớn và lồng nhau. So sánh nội dung (deep equality) sẽ tốn CPU không xác định. Identity comparison là O(1).
  • AI trả lời

    • AI thường nói: "In JavaScript, arrays are objects. The === operator checks for reference equality, not value equality. Each [] creates a new array object in memory, so they are different references."
    • AI có thể thêm: "If you want to compare contents, use JSON.stringify or a deep equality function."
    • Tuy nhiên, AI hiếm khi giải thích tại sao JavaScript thiết kế như vậy. AI thường trình bày như một "quirk" thay vì một "engineering trade-off".
  • So sánh

    • AI đúng về mechanism (reference equality).
    • AI thiếu: lý do thiết kế (performance, predictability) và hậu quả thực tế (mutation bug, cache contamination).
  • Điểm AI nói sai hoặc quá mơ hồ

    • Gợi ý dùng JSON.stringify để so sánh array là nguy hiểm:
      • JSON.stringify([1, 2]) === JSON.stringify([1, 2]) đúng, nhưng
      • Không hoạt động với object có property order khác, undefined, function, circular reference, hoặc NaN.
    • AI thường không cảnh báo rủi ro này.
  • Kết luận

    • Nếu bạn chỉ ra được rằng === với object là identity comparison vì lý do performance, và JSON.stringify không phải lời khuyên production-safe, bạn đã hiểu sâu hơn AI.
    • Concept này sẽ quay lại ở Stage 2 (Prototype & Object Model), Stage 8 (React props comparison, React.memo), và Stage 11 (Memory & Reference).

14. Teach Back (Dạy lại) ​

Giải thích cho một đồng nghiệp junior trong 2 phút, không dùng từ "nó thế":

"Tại sao khi tôi copy một object bằng const b = a;, sửa b lại làm a đổi theo? Và tại sao const clone = { ...a } thì không?"

Yêu cầu:

  • Dùng đúng từ: reference, identity, mutation, shallow copy.
  • Dùng analogy "ngôi nhà và địa chỉ".
Mô phỏng
  • Bạn nói
    • "Hãy tưởng tượng object là một ngôi nhà. const a = { x: 1 } nghĩa là tạo một ngôi nhà và ghi địa chỉ vào biến a."
    • "const b = a không tạo nhà mới. Nó chỉ ghi cùng một địa chỉ vào biến b. Giờ cả a và b đều chỉ đến cùng một ngôi nhà."
    • "Khi bạn sửa b.x = 2, bạn đang đến nhà đó và sơn lại tường. Vì a cũng chỉ đến nhà đó, a.x cũng thành 2."
    • "Còn { ...a } là shallow copy — nó xây một ngôi nhà mới, copy đồ đạc cấp 1 sang. clone giờ có địa chỉ riêng. Sửa clone không ảnh hưởng a."

💡 Liên tưởng: Gán object cho biến khác giống như bạn photo cái địa chỉ nhà. Photo không tạo ra ngôi nhà mới. Cả hai tờ giấy đều dẫn đến cùng một nơi. Shallow copy giống như xây nhà mới với nội thất giống hệt — nhưng là nhà riêng biệt.

Gợi ý đánh giá bản thân

  • Đồng nghiệp có hỏi "vậy nested object thì sao?" không? Nếu có, bạn đã truyền đạt đủ rõ để họ nhận ra giới hạn của shallow copy.
  • Bạn có thể dự đoán behavior của const arr = [obj1]; const arr2 = [...arr]; arr2[0].x = 2; không? (arr[0].x cũng đổi, vì spread array mới nhưng element vẫn là cùng reference.)

15. Assessment (Đánh giá) ​

Objective (Mục tiêu)Hình thức đánh giá
Giải thích tại sao object cùng nội dung không bằng nhauExplanation (Giải thích)
Dự đoán mutation lan sang variable khácPrediction (Dự đoán)
Phân biệt rebind và mutationPrediction + Explanation
Tạo shallow copy đúng lúcImplementation (Thực hành)
Nhận diện built-in object typesClassification (Phân loại)

16. Exit Criteria (Tiêu chí qua bài) ​

  • [ ] Có thể giải thích tại sao {} === {} và [] === [] đều là false.
  • [ ] Có thể dự đoán đúng 5/5 scenarios trong Prediction Exercise.
  • [ ] Có thể viết hàm trả về object mới mà không mutate input.
  • [ ] Có thể debug trường hợp shared reference gây lỗi cache hoặc default config.
  • [ ] Có thể phân biệt Date, Map, Set là object types.
  • [ ] Không còn nhầm lẫn const với immutability đối với object.

17. Spiral Connection (Liên kết xoắn ốc) ​

Previous (Trước): Lesson 0.2.3 — Primitive Types. Bạn đã biết primitive là atomic, immutable, so sánh bằng value.

Current (Hiện tại): Lesson 0.2.4 — Object Values. Bạn hiểu object có identity, so sánh bằng reference, mutation ảnh hưởng tất cả binding, shallow copy chỉ copy cấp 1.

Next (Tiếp theo):

  • Module 0.3 — Coercion & Equality (== vs === với object, truthy/falsy).
  • Stage 1 — Execution Model: Object reference là nền tảng để hiểu Closure capturing object.
  • Stage 2 — Object Model: Prototype chain, deep clone, property descriptor.
  • Stage 8 — React: useState với object, React.memo so sánh props by reference, immutable update patterns.
  • Stage 11 — Performance: Memory leak từ retained object references.
📴 Offline Mode — Content served from cache