面試中詢問 Vue2/Vue3 響應式差異,通常是為了評估工程師在實戰中的四個核心能力:
| 比較維度 | Vue 2 (Object.defineProperty) | Vue 3 (Proxy) |
|---|---|---|
| 攔截目標 | 針對「單一屬性(Property)」 | 針對「整個物件(Object)」 |
| 新增/刪除屬性 | 無法攔截 (需依賴 this.$set / $delete) |
原生支援 |
| 陣列操作 | 不支援索引修改 (需 hack push 等原型方法) |
原生支援 (包含修改長度、索引) |
| Map / Set 支援 | 完全不支援 | 原生支援 |
| 初始化效能 | 貪婪式 (Eager):需一次性遞迴遍歷整棵物件樹,大物件易卡頓。 | 懶加載 (Lazy):讀取深層屬性時才臨時代理,初始化極快。 |
| 原始物件引用 | obj === observed (直接修改原物件) |
obj !== observed (產生全新代理包裹器) |
| IE 支援度 | 支援 IE9+ | 不支援 IE (Proxy 無法被 Polyfill) |
實戰中,若直接操作大型巢狀物件,極易引發「參照污染」或「UI狀態被洗掉」的嚴重 Bug。解決這些問題的核心原則是:保持單向資料流,並嚴格區分「業務資料」與「展示狀態」。
當從 Store 取出資料放入表單綁定 v-model 時,若使用淺拷貝,會導致深層屬性依然共用記憶體,使用者的輸入會直接竄改全域狀態。
❌ 異常寫法(淺拷貝陷阱):
// ❌ 這樣寫,當使用者在 input 輸入時,Store 裡面的 profile.name 會同步被改掉!
// 甚至還沒點擊「儲存」,全域狀態就已經被污染了。
const formData = ref({ ...store.user });
✅ 正確解法: 使用深拷貝(structuredClone)徹底切斷關聯
<template>
<input v-model="formData.profile.name" />
<button @click="save">儲存</button>
<button @click="cancel">復原</button>
</template>
<script setup>
import { ref } from 'vue';
import { useUserStore } from '@/stores/user';
const store = useUserStore();
// ✅ 使用原生 API 進行深拷貝,產生毫無關聯的新物件
const formData = ref(structuredClone(store.user));
const save = () => { store.updateUser(formData.value); };
const cancel = () => { formData.value = structuredClone(store.user); };
</script>
後端回傳原始資料(如 1500.5),前端需要格式化(如 $1,500.50)。若拿 API 資料直接暴力覆蓋 State,辛苦算出的格式化欄位會瞬間蒸發。
❌ 異常寫法(將 UI 狀態與業務資料混在一起):
// ❌ API 回來直接覆蓋,原本自己加的 formattedAmount 欄位就不見了
async refreshBalance() {
const response = await api.getBalance();
// response.data 只有 { asset: 'USDC', rawAmount: 1500.5 }
this.wallet = response.data;
}