之前談到純函數的第二個要件是不能對可改變(Mutable) 資料做變更,那在 Javascript 中有哪些資料型態屬於 Mutable 資料型態呢?我們先來看 Javascript 的資料型態
Javascript 總共有七個資料型態
到底 Mutable 跟 Immutable 有什麼區別?先來看 Immutable 的例子
// for number
let a = 3;
let b = a;
b = b + 4;
console.log('b = ', b); // 7
console.log('a = ', a); // 3
// for string
let c = 'This is original string';
let d = c;
d = 'Now change the string';
console.log('d = ', d); // Now change the string
console.log('c = ', c); // This is original string
codepen
當其他變數(例如 b
, d
) 參照到原來變數 (a
, c
) 時,系統會在 Memory 指派一個新的位址,不會影響到原來的變數,那 Mutable 呢?
// for object
let e = { name: 'John', age: 40, title: 'Engineer'};
let f = e;
f.title = 'Manager';
console.log('f = ', f); // { name: 'John', age: 40, title: 'Manager' }
console.log('e = ', e); // { name: 'John', age: 40, title: 'Manager' }
可以發現新的變數(f
)還是指到原來變數(e
)的位址,所以當f
改變時,e
也跟著改變了,這是為了系統的優化而作的。然而不小心的話,可能會造成程式的臭蟲(bug), 而很難去追蹤,想像同一個物件共用在不同的地方(在 Angular 不同的 Component 或 module 中),當一個地方改變這個物件時,其他地方如果不注意,沒有隨之更改(比如說狀態),就會造成使用者的困擾,而這有時候很難去除錯。
Javascript 的物件有包含了很多類型,幾個常見的
而這些在我們處理純函數時,都要特別留意小心。
讓我們來看如何處理先前物件的例子
// immutable
let g = { name: 'Mary', age: 38, title: 'Engineer'};
let h = Object.assign({}, g, { title: 'Manager'});
console.log('h = ', f); // { name: 'Mary', age: 38, title: 'Manager' }
console.log('g = ', g); // { name: 'Mary', age: 38, title: 'Engineer'}
使用Object.assign()
來產生一個新的物件,它的語法是Object.assign(target, ...sources)
,我們的 target 是一個新的空的物件 {}
, sources 是 g
跟{title: 'Manager'}
,將 sources 結合後放入 target 中。這樣產生出來的變數h
就跟原來的g
脫勾了。
這個做法相當重要,往後我們在 Reducer 中,經常會見到這樣的語法。
Javascript 的陣列是特殊的物件,當然也是 Mutable,比較特殊的是陣列有些 Operators 是 immutable的,例如 .map .filter .concat...
,這些 Operators 會產生新的陣列,不會影響舊的陣列,我們可以直接用在純函數中。
然而有一些 Operator 是 mutable 的,例如 .push .pop .sort...
,這些會直接改變原本的陣列,我們在純函數中就不該使用。
但是如果我們要加一個元素到陣列時,該怎麼做?看以下例子
// for array
let i = [1, 2, 3, 4];
let j = i;
j.push(5);
console.log('j = ', j); // [1, 2, 3, 4, 5]
console.log('i = ', i); // [1, 2, 3, 4, 5]
// immutable array
let k = [1, 2, 3, 4]
let l = [...k, 5];
console.log('l = ', l); // [1, 2, 3, 4, 5]
console.log('k = ', k); // [1, 2, 3, 4]
如果直接用 .push
會影響原來陣列,這就不能用在純函數,而使用擴展語法 spread syntax,會產生新的陣列,不會對原本陣列產生影響。
擴展語法是一個相當強大的語法,有提議將這種語法擴充到物件上,雖然目前瀏覽器的支援還不夠,但因為我們使用 Angular typescript,它會幫我們做轉換,所以還是可以安心使用,舉個例子,之前的 let h = Object.assign({}, g, { title: 'Manager'});
可以寫成 let h = {...g, title: 'Manager'}
現在,我們對於純函數有了一些工具來建置,接下來我們來深入探討一下 ngrx/store 如何實作 Flux 的概念。
筆誤
// immutable
let g = { name: 'Mary', age: 38, title: 'Engineer'};
let h = Object.assign({}, g, { title: 'Manager'});
console.log('h = ', f); // { name: 'Mary', age: 38, title: 'Manager' }
這邊帶入的應該是 h
console.log('g = ', g); // { name: 'Mary', age: 38, title: 'Engineer'}