做首頁卡片編輯器時,先建立三筆空資料,改第一張的標題,另外兩張卻一起變了。若初始化寫的是 Array(3).fill({ title: '' }),可以先查資料本身:三個位置很可能指向同一個物件。
要讓每張卡片各自保存內容,建立陣列時就要逐筆建立新物件。Array.from({ length: 3 }, () => ({ title: '' })) 適合這種情況;關鍵是回呼函式每次都回傳剛建立的物件。
先重現:改第一張,三張標題都變成「首頁」
以下是簡化的卡片資料,不需要連上網站或安裝套件。在瀏覽器開發者工具的 Console,或 Node.js 中執行即可:
const cards = Array(3).fill({ title: '' });
cards[0].title = '首頁';
console.log(JSON.stringify(cards.map(card => card.title)));
// ["首頁","首頁","首頁"]
console.log(cards[0] === cards[1]);
// true
fill() 會把傳入的值填進每個位置。這裡的 { title: '' } 只建立一次;陣列有三個位置,卻沒有三個獨立物件。透過第一個位置修改 title,另外兩個位置讀到的仍是同一個物件。
用 === 比較兩個物件,可以確認它們是否指向同一個物件。若結果是 true,就先修初始化資料,再回頭查畫面更新邏輯;重新渲染並不會把共用的物件拆開。
每次建立一筆,連巢狀設定也各自保存
把初始化改為下面的寫法。例子加上常見的勾選設定,讓標題與巢狀物件都能一起檢查:
const cards = Array.from({ length: 3 }, () => ({
title: '',
settings: { selected: false }
}));
cards[0].title = '首頁';
cards[0].settings.selected = true;
console.log(JSON.stringify(cards.map(card => card.title)));
// ["首頁","",""]
console.log(JSON.stringify(cards.map(card => card.settings.selected)));
// [true,false,false]
console.log(cards[0] === cards[1]);
// false
console.log(cards[0].settings === cards[1].settings);
// false
Array.from() 的第二個參數會逐筆產生陣列元素。在這段程式中,每次呼叫都會建立新的卡片物件,以及新的 settings 物件,所以改第一張的勾選狀態,不會改到第二張。
若卡片欄位較多,可以把建立單筆資料的程式整理成函式,再把函式傳給 Array.from()。函式內仍應每次建立新物件,尤其要留意陣列、設定物件等會被修改的欄位。
改用 Array.from,卻仍回傳同一份範本?
只換方法名稱還不夠。下面先建立 template,回呼函式每次都交回同一個物件,三筆資料仍然共用它:
const template = { title: '' };
const cards = Array.from({ length: 3 }, () => template);
cards[0].title = '首頁';
console.log(cards[0] === cards[1]);
// true
console.log(cards[1].title);
// 首頁
Array.from() 沒有自動替你深複製物件。要改的是回傳值的建立方式,而不是把 fill() 換成另一個呼叫後,就認為資料已經獨立。
展開運算子只複製外層,巢狀物件仍要留意
若範本裡有設定物件,{ ...template } 只會建立新的外層物件,裡面的 settings 仍可能共用。下面的兩個比較結果不同:
const template = { title: '', settings: { selected: false } };
const cards = Array.from({ length: 3 }, () => ({ ...template }));
console.log(cards[0] === cards[1]);
// false
console.log(cards[0].settings === cards[1].settings);
// true
cards[0].settings.selected = true;
console.log(cards[1].settings.selected);
// true
若每張卡片的設定必須獨立,就像前面的修正例子一樣,在回呼內直接建立 settings: { selected: false }。當欄位結構明確時,逐層建立需要修改的資料,比猜測哪一層已複製更容易維護;更深層的物件也要按實際結構處理。
修好後,用兩筆資料試一次:改第一筆的文字,再改它的巢狀設定,確認第二筆都維持原值。這能分別查出外層物件共用,以及「外層不同、內層共用」兩種問題。
封面為 AI 生成的概念插畫,用旋鈕與卡片示意資料共用關係。
