← JavaScript Learning Path

JAVASCRIPT V2 · ADVANCED · 127

Map / Set / Identity:先理解「同一個值」到底是什麼

Array、Object、Map、Set 都能裝資料,但它們表達的問題不同。這一課的核心不是背 API,而是建立 primitive equality、object reference identity、uniqueness 與 key-value mapping 的正確模型。

Learning outcomes

1. Why now:資料多了之後,Array 不一定是最自然的模型

const selectedIds = [1, 4, 9];

// 問:id 4 是否已選?
// 問:如何確保沒有重複?

如果核心問題是 membership 與 uniqueness,Set 的語意比「array 但我們約定不准重複」更精確。

2. Mental model:Primitive 比 value,Object 比 identity

1 === 1
// true

"JS" === "JS"
// true

{} === {}
// false

兩個 object literal 內容相同,仍是兩個不同 objects。

3. Worked Example A:Alias 與 Copy

const a = {
  name: "Amy"
};

const b = a;

b.name = "Bob";

console.log(a.name);
// Bob

a 與 b 指向同一個 object identity。這不是「const 被改掉」,而是 object 內容被 mutation。

4. 淺複製建立新外層 object,但 nested reference 仍可能共享

const a = {
  profile: {
    city: "Tainan"
  }
};

const b = { ...a };

b.profile.city =
  "Kaohsiung";

console.log(
  a.profile.city
);
// Kaohsiung

spread 只建立新的 outer object;profile reference 仍相同。

5. Worked Example B:Set 表達唯一集合

const tags =
  new Set();

tags.add("javascript");
tags.add("javascript");
tags.add("debug");

console.log(tags.size);
// 2

tags.has("debug");
// true

Set 很適合「是否存在」「不重複集合」。如果你需要 index-based order operations,Array 仍可能更合適。

6. Set 對 Object 也依 Identity 去重

const a = { id: 1 };
const b = { id: 1 };

const set =
  new Set([a, b]);

console.log(set.size);
// 2

Set 不會自動根據 id 欄位判斷「業務上相同」。如果 domain identity 是 id,應自己以 id 建索引。

7. Worked Example C:Map 做 ID index

const byId =
  new Map(
    students.map(
      student => [
        student.id,
        student
      ]
    )
  );

const student =
  byId.get(42);

Map 的 key 可以是 number、object 或其他 value;API 也直接表達 has/get/set/delete。

8. Object key 與 Map key 的語意差異

const obj = {};
obj[1] = "one";

console.log(
  Object.keys(obj)
);
// ["1"]
const map = new Map();
map.set(1, "number");
map.set("1", "string");

console.log(map.size);
// 2

plain Object 仍非常適合固定 shape 的 record;Map 則更像真正的 dynamic key-value collection。

9. Common mistakes

A. 用 JSON.stringify 判斷所有 object equality

property ordering、unsupported values、cycles 與 domain semantics 都會讓這方法不可靠。

B. 以為 spread 是 deep clone

nested objects 仍可能共享 identity。

C. 用 Set 去除「相同 id 的 objects」

不同 object references 仍會同時存在。

D. 只因 Big-O 就選 Map

先看問題語意與資料量,再談 complexity。

10. Debug evidence:Reference bug 要印 identity 關係

console.log(
  original === copy
);

console.log(
  original.profile
  === copy.profile
);

看到「改 copy 卻污染 original」,先分層確認 outer identity 與 nested identity,而不是先怪 spread operator。

11. Guided exercise:Selected Student IDs

const selected =
  new Set();

function toggle(id) {
  // TODO
}

要求:存在就 delete,不存在就 add;回傳目前是否已選取。

12. Independent exercise:建立 Map index

從 student array 建立 id → student Map,支援 lookup、update index、delete。再說明如果 student.id 改了,index 需要如何維護。

function indexById(
  students
) {
  // return Map
}

13. Challenge:Domain equality

兩筆 student objects 只要 id 相同就視為同一學生。請寫 dedupeById(students),不能依賴 Set 對 object 本身去重。

const input = [
  { id: 1, name: "Amy" },
  { id: 1, name: "Amy updated" }
];

Project checkpoint:Grade Tracker Selection + Index

const selectedIds =
  new Set();

let studentsById =
  new Map();

function rebuildIndex(
  students
) {
  studentsById =
    new Map(
      students.map(
        s => [s.id, s]
      )
    );
}

現在 UI selection 與 data lookup 各自有符合語意的資料結構,而不是所有需求都塞進一個 Array。

14. Vocabulary / Summary

15. Further reading

Knowledge check · Mastery

  1. 解釋為何 {id:1} === {id:1} 是 false。
  2. 說明 shallow copy 的 nested reference 風險。
  3. 完成 Set toggle 與 Map index。
  4. 完成頁面下方 Executable JS identity tests。