其實應該不限於 Vue,這是 JavaScript 語言的基礎特性,怪我不用功越級打怪就一直在還債QQ

案發經過

使用 Vue 的computed屬性實作一些驗算 function ,僅是對 data 物件裡的某個 array 作 forEach 另存,竟會影響到原本的 data,就算切成 component 使用 props 傳值也一樣。跟之前用 React ,傳值的表現完全不同。

然後就翻到 Vue 的文檔,明確告知要注意:

Note that objects and arrays in JavaScript are passed by reference, so if the prop is an array or object, mutating the object or array itself inside the child will affect parent state.

JS的資料型別

參考簡單介紹JavaScript參數傳遞

JavaScript 中,原始型別 (primitive type) 如 string, number, boolean 這些只有純值而沒有其他 method 的資料,當我們將一個變數參照至它時,這個變數的記憶體會寫入一樣的 value ,也就是 by value 的傳值方式,如同複製光線這很直覺:

1
2
3
4
5
6
7
var a = 'I am a string';
var b = a;
b = 'change this';
console.log(a, b);
/*
"I am a string" "change this"
*/

object ( array , function 在JS中也是 object ) 這類資料的傳值方式則是 by reference 。當我們用一個變數參照至它時,這個變數並不會另存一個一樣的 object ,而是存一個指標,指向原本 object 在記憶體上的 address,因此我們以為有兩個長得一樣但不相干的 object,實際上卻是像捷徑讓參照過它的變數都會連到同一個 object,稱為淺拷貝,然後就在修改它的時候產生認知失調

1
2
3
4
5
6
7
8
9
10
11
var objA = { words: 'I am a string' };
var objB = objA;
objB.words = 'change this';
console.log(objA, objB);
/*
Object {
words: "change this"
} Object {
words: "change this"
}
*/

其實就跟替身受傷,本人會跟著受傷是一樣的。

暴力破解

1
var cloned_array = JSON.parse( JSON.stringify(arrayData) );

用Json互轉能達成深拷貝,不過例外是 function 無法被轉換。function 在 JavaScript 國是一等公民、是個 object,可是 json 國不承認,就把它給消失惹。而且互轉似乎很耗效能,但對我目前只處理一組Array的情況是足夠了。

更多好的深拷貝方法可以看看這篇網路大大寫的淺拷貝與深拷貝,解釋得很詳盡。jQuery 有 $.extend() ,lodash 有 _.cloneDeep 可以用。

所以那個React

React 的 prop 傳值行為,標準的父傳子,子不影響父,猜測應該中間就處理好了深拷貝。Vue 實例內部是靠 this.someData 賦值,傳 array 或 object 一定是 pass by reference,作解耦要嘛子元件接 prop 進來時深拷貝一次,或是父元件用深拷貝過的data 傳給 prop 。

似乎也有用單一組件直接管理、傳送所有深拷貝過的 Data 的作法,啊那不就是 flux 架構? 等 code 有大到需要用 Redux、Vuex 再說啦…

參考資料