iT邦幫忙

2026 iThome 鐵人賽

DAY 6
0
JavaScript

QA 的 JavaScript 初心之路:30 天做出一個能玩的戰鬥小遊戲《勇者 vs 小怪》系列 第 6

Day06|物件登場:const 鎖不住的內容,鎖不住的陣列

  • 分享至 

  • xImage
  •  

今天的主題:物件(Object)——戰鬥遊戲的靈魂正式登場

如果說陣列是「一排資料」,物件就是「一群有名字的資料」,用大括號 {} 宣告,每筆資料都是 key: value 的配對:

const hero = { name: "勇者", hp: 100, atk: 20, def: 5 };
const enemy = { name: "小怪", hp: 60, atk: 15, def: 3 };

比起用一堆分散的變數(heroNameheroHpheroAtk……),物件把「同一個角色」的所有資料打包成一包,管理起來更直覺。用點記法 . 存取:

console.log(hero.name, "的 HP:", hero.hp);   // 勇者 的 HP: 100
console.log(enemy.name, "的 HP:", enemy.hp); // 小怪 的 HP: 60

補充一下:JS 的物件概念上跟 Python 的字典(dict)一樣,都是 key-value 結構,只是 Python 習慣用中括號存取(hero["name"]),JS 習慣用點記法(hero.name)。

不過 JS 其實也支援中括號寫法,hero["name"]hero.name 效果完全一樣,只是點記法比較簡潔、業界也更常用這種寫法。中括號寫法還有一個點記法做不到的用途:當屬性名稱是存在變數裡的字串時,只能用中括號,例如 hero[someKey]——這也是今天迴圈清空練習裡 hero[keys[i]] 用中括號而不是點記法的原因。

揭曉 Day 2 的伏筆:const 物件的內容其實能改

還記得 Day 2 埋的伏筆嗎?「const 鎖住的是變數指向誰,不是內容完全不能動」——今天親自驗證了這件事。

先修改物件內部的屬性:

hero.hp = hero.hp - 30;
console.log("勇者受傷後 HP:", hero.hp);   // 70

這樣是合法的,const 沒有阻止我修改 hero 裡面的內容,只是不能把 hero 整個換成別的物件。

延伸測試:陣列的 const 規則跟物件一樣嗎?

物件驗證完之後,我很好奇陣列是不是也是同一套規則——畢竟陣列本質上也是一種物件(還記得 Day 5 挖到的 [[Prototype]] 嗎?)。

const arr = ['你', '我', '他'];

arr[0] = '0';
arr[1] = '1';
arr[2] = '2';

console.log(arr);

結果證實規則完全一致:一格一格改是合法的,const 沒有抗議,因為 arr 從頭到尾都指向同一個陣列,只是內容變了。如果改成 arr = ['0','1','2'] 這種整個重新賦值的寫法,就會跟物件一樣噴出 TypeError

意外踩到 Console 的一個陷阱

驗證這個的時候,我發現一件很奇怪的事:我在改值之前就先 console.log(arr) 一次,理論上應該印出 ['你','我','他']。但當我點開這行紀錄展開來看細節時,裡面顯示的卻是 0: "0", 1: "1", 2: "2"——跟我印的當下明明不一樣!

查了才知道:console.log() 印物件或陣列時,存的不是「當下那一刻的快照」,而是一個「活的參照」。摺疊起來的預覽文字(像 (3) ['你', '我', '他'])確實是 log 當下拍的快照,但點開展開查看內部細節時,Console 會重新讀取這個陣列「現在」的真實狀態——如果程式碼在那之後又把陣列改了,點開看到的就會是改過的新內容,不是當初 log 那一刻的內容。

插入 day6-2.png:兩次 console.log(arr) 展開後都顯示改過的值,連改值之前那次也一樣

這不是 JavaScript 語言本身的行為,是瀏覽器開發者工具顯示機制的特性,但是個很多工程師都踩過的坑。如果之後想在 Console 裡真正凍結某個時間點的快照,我也順手測了兩種解法,確認都真的有效:

console.log([...arr]);              // 複製一份新陣列,之後原陣列再怎麼改都不影響這份紀錄
console.log(JSON.stringify(arr));  // 轉成字串,鎖住當下的值

插入 day6-3.png:兩種解法展開後都正確顯示原始值 你/我/他,證實有效

新增、刪除:都是合法的「修改內容」

延伸這個觀念,我測了新增和刪除:

// 新增一個屬性
hero.skill = "火球術";
console.log("新增後:", hero);

// 刪除一個屬性
delete hero.atk;
console.log("刪除 atk 後:", hero);

結果:

新增後: {name: '勇者', hp: 70, atk: 20, def: 5, skill: '火球術'}
刪除 atk 後: {name: '勇者', hp: 70, def: 5, skill: '火球術'}

新增用直接賦值就能加(hero.skill = ...),刪除要用 delete 這個運算子——兩者都是在動「內容」,不是換掉整個變數,所以對 const 來說都合法。

插入 day6.png:完整流程截圖,含新增/刪除/逐步清空/最後 TypeError

用迴圈逐步清空,親眼看著內容一個一個消失

JS 沒有像陣列那樣提供「一鍵清空物件」的功能,想清空只能一個一個刪除鍵。我用 Object.keys() 把所有鍵名抓出來,搭配迴圈逐一刪除,每刪一次就印出剩下多少:

const keys = Object.keys(hero);
for (let i = 0; i < keys.length; i++) {
  delete hero[keys[i]];
  console.log(`刪除 ${keys[i]} 後,剩下:`, hero);
}
console.log("最終結果:", hero);

(這裡的 for 迴圈語法明天才會正式教,先照著打,有個初步印象就好。)

結果完整記錄了物件一步步被清空的過程:

刪除 name 後,剩下: {hp: 70, def: 5, skill: '火球術'}
刪除 hp 後,剩下: {def: 5, skill: '火球術'}
刪除 def 後,剩下: {skill: '火球術'}
刪除 skill 後,剩下: {}
最終結果: {}

從頭到尾,hero 都是同一個變數、同一個物件,只是裡面的內容越來越少——這正好證明「清空內容」跟「重新賦值」是兩種完全不同的操作

最後驗證:整個換掉真的不行

物件清空之後,我試著把 hero 整個換成一個全新的物件:

hero = { name: "復活勇者" };

結果:

Uncaught TypeError: Assignment to constant variable.

Day 2 的伏筆到這裡正式驗證完畢:不管物件裡面的內容怎麼新增、刪除、清空,只要不是「把整個變數重新指向另一個物件」,const 都不會抗議;但只要做了「整個換掉」這個動作,馬上噴錯。

小結

今天搞懂的事:

  • 物件是「有名字的資料群」,用 . 存取屬性,概念上等同 Python 的字典
  • const 只鎖「變數指向誰」,物件內部的屬性可以自由新增、修改、刪除
  • 新增用直接賦值,刪除用 delete,清空要靠迴圈逐一刪除,沒有一鍵完成的方法
  • 只有「把整個變數重新指向另一個物件」才會違反 const,這點在今天的測試裡得到完整驗證
  • 陣列跟物件是同一套 const 規則;console.log 印物件/陣列是活的參照不是快照,想凍結當下的值要用 [...arr]JSON.stringify()

明天繼續學迴圈——今天先偷用了 for,明天正式把它學好。


上一篇
Day05|陣列與索引:爬樓梯還是搭電梯?
下一篇
Day07|迴圈:我的主控台跑了 106082 次還停不下來
系列文
QA 的 JavaScript 初心之路:30 天做出一個能玩的戰鬥小遊戲《勇者 vs 小怪》7
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言