Skip to content

Lesson 0.6.6 — Object Utilities ​

Bài 0.6.6 — Object Utilities
Object.keys, values, entries & Transform object ↔ array • 25 phút
0:00 / 0:00

0. Metadata ​

FieldValue
Stage0 — JavaScript Language Foundation
Module0.6 — Data Structures
Lesson0.6.6
CompetencyC01.6 — Arrays / Objects
Depth TargetL2–L3
PrerequisitesObjects (0.6.5), Array Iteration (0.6.3), Reduce (0.6.4)
Estimated Cognitive LoadMedium

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:

js
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, reduce trên array (0.6.3, 0.6.4).
  • Hiểu array là object nhưng có length và index số (0.6.2).
  • Biết const và 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ể:

  1. Sử dụng Object.keys, Object.values, Object.entries để trích xuất dữ liệu từ object.
  2. Phân biệt Object.keys (chỉ own enumerable string keys) và for...in (bao gồm inherited).
  3. Chuyển đổi object ↔ array bằng entries + reduce (hoặc fromEntries nếu environment hỗ trợ).
  4. Viết function transform object values mà không mutate object gốc.
  5. Dự đoán thứ tự của keys trong kết quả.
  6. Nhận diện khi nào dùng Object.values so với Object.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.entries là cầu nối mạnh nhất vì mang cả key lẫn value sang array.
  • Object.keys hữu ích khi bạn chỉ cần tên property.
  • Object.values hữ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 PropertyProperty thuộc trực tiếp object, không phải từ prototype chain.
EnumerableProperty có thể được liệt kê trong vòng lặp.

Supporting (Hỗ trợ) ​

ConceptÝ nghĩa
Enumeration OrderString keys số đi trước theo thứ tự số, sau đó string keys khác theo insertion order.
for...inDuyệt tất cả enumerable properties bao gồm inherited. Cần hasOwnProperty check.

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

ConceptLý do chưa đào sâu
Object.getOwnPropertyNamesLiệt kê cả non-enumerable. Thuộc Stage 2 (Descriptors).
Reflect.ownKeysLiệ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.

js
const config = {
  apiUrl: "https://api.example.com",
  timeout: "5000",
  retries: "3",
};

Yêu cầu:

  1. Chuyển tất cả giá trị thành số nếu có thể parse.
  2. Giữ nguyên key.
  3. Không mutate config gốc.
js
const parsed = {};
for (const key in config) {
  if (config.hasOwnProperty(key)) {
    const num = Number(config[key]);
    parsed[key] = isNaN(num) ? config[key] : num;
  }
}
js
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 ​

js
const obj = { b: 2, a: 1 };
console.log(Object.keys(obj));

Câu 2 ​

js
const obj = { x: 10, y: 20 };
const values = Object.values(obj);
values.push(30);
console.log(obj);

Câu 3 ​

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

js
const arr = [
  ["name", "Alice"],
  ["age", 24],
];
const obj = Object.fromEntries(arr);
console.log(obj.name);
console.log(obj["age"]);

Câu 5 ​

js
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.keys trả về own enumerable string keys theo insertion order (trừ numeric keys). Trong object literal { b: 2, a: 1 }, b được insert trước a, 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.values tạo array mới. values.push(30) mutate array values, không affect obj. obj không đổi.

Câu 3: { a: 2, b: 4 } — entries → map → fromEntries

  • Giải thích: entries → [["a", 1], ["b", 2]]. map double value → [["a", 2], ["b", 4]]. fromEntries → object mới. obj gốc không đổi.

Câu 4: "Alice" và 24 — fromEntries tạo object

  • Giải thích: Object.fromEntries biế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.

js
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]
js
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 đổi

Giải thích:

  • Object.entries tạo array các [key, value].
  • map biến mỗi pair thành [key, formatter(value)].
  • Object.fromEntries gom array pairs trở lại object.
  • prices gố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.

js
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]
js
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.entries lấy tất cả [key, value].
  • filter giữ lại pair mà predicate trả về truthy.
  • Object.fromEntries gom lại thành object.
  • Pattern này tương đương Array.prototype.filter như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):

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

Giả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 qua keyMapper, giữ value.
  • sumValues: Object.values lấy array values, reduce tí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

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

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

js
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

js
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

js
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

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

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

js
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._secret thành undefined sau khi gọi sanitizeConfig.
  • clean và cfg là object khác nhau nhưng cfg bị 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):

js
console.log("_secret" in cfg); // false — đã bị xóa

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

js
function sanitizeConfig(config) {
  return Object.fromEntries(
    Object.entries(config).filter(([key]) => !key.startsWith("_")),
  );
}

Prevention (Phòng ngừa):

  • Nghi ngờ mọi delete trong 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 + fromEntries là 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.

js
function maxKey(obj) {
  return Object.entries(obj).reduce(
    (max, [k, v]) => {
      return v > max.value ? { key: k, value: v } : max;
    },
    { key: null, value: -Infinity },
  );
}
js
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:

  1. Option A duyệt array mấy lần?
  2. Option B có vấn đề gì về performance?
  3. Nếu object có 10,000 entries, bạn chọn cái nào?
[Đáp án tham khảo]
  • Bạn nghĩ:

    1. Option A duyệt một lần qua entries. reduce tính toán tại chỗ.
    2. Option B sort duyệ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.
    3. Option A. O(n) vs O(n log n). Sort là overkill cho "find max".
  • Kết luận: Chọn algorithm phù hợp intent. reduce cho aggregation single-pass; sort cho 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:

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

js
{
  "data.user.name": "Alice",
  "data.user.role": "admin",
  "data.settings.theme": "dark",
  "data.tags": ["developer", "admin"]
}

Code hiện tại:

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

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

  1. 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?

  2. typeof value === "object" có bắt được array không? Hậu quả là gì?

  3. Có trường hợp nào khác mà typeof value === "object" gây ra hành vi không mong muốn?

  4. 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ĩ:

    1. for...in có 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ả.

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

    3. null cũng có type object. Vì vậy chỉ kiểm tra typeof value === "object" là chưa đủ để xác định value có phải nested object hay không.

    4. Có thể viết lại bằng Object.entries() và kiểm tra rõ null + array:

      js
      function 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ơn for...in khi 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ần typeof value === "object" để xác định nested object vì array và null cũ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

  1. 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ới Object.keys.

    js
    function Person(name) {
      this.name = name;
    }
    Person.prototype.greet = function () {};
    
    const p = new Person("Alice");
  2. Hỏi AI: "Liệt kê tất cả keys của p bằng for...in và Object.keys. Tại sao kết quả khác nhau?"

  3. 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?

  4. Verify bằng MDN: tìm Object.keys và for...in trê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...in liệt kê cả name (own) và greet (inherited từ prototype). Object.keys chỉ liệt kê name (own enumerable). Object.entries và Object.values cũng chỉ own enumerable.

  • AI trả lời (typical):

    • for...in iterates over all enumerable properties including inherited ones.
    • Object.keys only returns own enumerable properties.
    • Object.keys(p) will only return ["name"].
    • for...in will return name and greet.
  • 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...in khô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/values là deliberate design để tránh prototype pollution, và for...in chỉ 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ết Object.keys(obj).map(...) rồi còn phải tra lại obj[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.entries chính là "lấy ra", Object.fromEntries là "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.keys thay 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 LabDebug Lab Section
Choose reduce vs sort for aggregation (Chọn algorithm)Design DecisionDesign Exercise
Explain object/array bridgeTeach BackTeach 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ằng Object.fromEntries hoặc reduce.
  • [ ] 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 delete trong iteration.
  • [ ] Có thể chọn Object.keys vs Object.entries vs Object.values phù 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 } = obj là shorthand cho obj.keys.
  • 0.6.8 (Spread & Shallow Copy) — { ...obj } tạo object mới. Kết hợp với entries để transform object mà không mutate.
  • Stage 2 (Object Model) — Prototype chain: tại sao for...in thấ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.
📴 Offline Mode — Content served from cache