作者簡介:hustcc 螞蟻金服·資料體驗技術團隊
immutable 是什麼?不變的、一成不變的。在 Javascript 中一般指一個變數在經過一個 function 處理之後,可以保持入引數據不變。
一、什麼是 immutable?
舉個真實日常例子:前兩天,在業務程式碼中,需要獲得資料的中位數,並使用 echarts 繪製出中位線,輔助分析。寫程式碼之前,先找了一下中位線的定義。
對於有限的數集,可以通過把所有觀察值高低排序後找出正中間的一個作為中位數。如果觀察值有偶數個,通常取最中間的兩個數值的平均值作為中位數。
哦,挺簡單,於是奮筆疾書(其實是 github 上的程式碼片段)
/** * 求取陣列中的中位數 * [1, 2, 3, 4, 4] ->
3 * [1, 2, 3, 4, 5, 6] ->
3.5 */const calculateMedian(arr) =>
{
// 1. 排序
arr.sort((a, b) =>
a - b);
const half = Math.floor(arr.length / 2);
// 2. 按照中位數定義計算
// 奇數長度則中間值,偶數長度則中間兩個數的平均值
return arr.length % 2 !== 0 ? arr[half] : (arr[half - 1] + arr[half]) / 2.0;
}複製程式碼
這段程式碼的問題在於函式是 mutable 的:
當經過函式 calculateMedian 求取陣列 [5, 4, 3, 2, 1] 的中位數的時候,導致入引數組變成了 [1, 2, 3, 4, 5]。導致後續的程式碼拿到的陣列,都是經過排序的,資料都是混亂的。
上述程式碼只需要修改一個地方即可:
const newArr = [].concat(arr).sort((a, b) =>
a - b);
複製程式碼
其中 [].concat(arr)
達到的效果就是程式碼的 immutable。
通過這樣的一個實際例子,我想應該很容易理解 mutable 程式碼造成的影響是什麼?
- 資料汙染:經過一個方法,資料就變化了,多麼可怕。
- 資料不可控:資料不僅僅是被修改,而且我們沒法控制資料被修改的規律。
- debug 黑洞:一般的 bug,如果瞭解業務,可能不用 debugger 就能定位一二;mutable 產生的 bug,沒辦法,新增程式碼一句句 F10 吧。
二、常見的原因
很多情況下,道理我們都懂,但是還是會不經意寫出 mutable 的程式碼。分為幾種情況,看起來清晰一些吧!
1. 意識不清晰
很多開發者在寫程式碼的時候,沒有從全域性勝寒意識到函式必須保證 immutable,只在乎自己的函式輸入輸出符合要求。
開發者需要有時刻警惕的意識,一旦涉及到 object 的操作,先提醒自己注意不可變。特別是對於公共模組的開發,開源專案的開發,養成“變道就打方向燈的好習慣”。
2. 函式理解不清晰
Array 常見的函式 push、pop、shift
等,一般都能理解它們是會修改原陣列資料,是 mutable 的函式。
但是對於上述例子中的 sort
函式應該就是理解上容易產生歧義(自己之前一直以為是生成一個新的陣列,寫完這篇,我去搜尋了整個業務程式碼)。同樣的,容易產生歧義的函式包括:
- sort
- reverse
- fill
- splice
- delete
注意:Array 中 immutable 函式很多,以上幾個函式是功能上容易誤解的。
3. 使用“不靠譜”的開源庫
GitHub 真是個好東西,只要你能想到的程式碼,基本都有參考的,特別是 JavaScript 語言的。
但是你永遠不知道你使用的輪子是不是僅僅是一個課堂作業練手的。在使用一個開源模組的時候,需要觀察:
- 單測是否完善:不是指單測是不是綠色(pass),而是看單測程式碼是否靠譜。
在完善的單測可以看到:
- 專案實際的使用方式,這些很可能是 README 中無法完整寫出來的東西。
- 理解專案的模組劃分,組織架構。
- 開發者在專案程式碼中扣住的開發細節點。
比如,如果 immutable 是專案的一個重要特性之一,那麼在單測程式碼中一定會反映出來。
- npm 下載量
就像在網上購物一樣,比較懶的購物者,直接買訂單數最多的爆款。
- issues
可以關注在 issue 處理速度、issue 中搜尋關鍵字,可以大概知道是否存在這些問題。
- 何妨 review 一下
如果程式碼簡單,可以簡單 review 一下,抓住程式碼實現的關鍵點。
三、如何寫 immutable 的程式碼
既然要寫 immutable 的程式碼,那我們應該怎麼做?
1. 靠譜開源專案
- immutability-helper:基於原始 Array 和 Object 的 immutable 操作,很好用。
- js-joda:日期 Date immutable 方法。
- immutable-js:Facebook 的 immutable 模組,定義了新的資料結構,使用成本相對高一點點。
- immutability-helper-x:包裝 immutable-helper,使用更友好。
- immu:基於 Proxy 的 immutable 模組,思路新穎,但是相容性差一點。
2. 單測約束
針對開頭的 calculateMedian
函式,寫一個單測:
describe('calculateMedian', () =>
{
test('immutable', () =>
{
const a = [5, 4, 3, 2, 1];
const aClone = a.slice();
expect(calculateMedian(a)).toBe(3);
// 保證不能被修改! expect(a).toEqual(aClone);
});
});
複製程式碼
單測約束之後,以後其他人修改這麼程式碼導致 mutable 的時候,也會 ci 報錯的,保證這個方法的長治久安。
除此之外,開啟 Eslint 工具,也可以對 Object 的 mutable 自動告警。
3. 解構和 spread 語法
解構是 spread 語法是 ES6 中的新語法,也是我個人用的非常多的,一般程式碼的 immutable 寫法,都可以滿足。
- Object 解構與 spread
比如我們有一個使用者資訊 A(幾十項資訊),然後需要建立一個使用者資訊 userB,除 id、name 不一樣之外,其他都和 userA 一致。
const userA = {
id: 1, name: 'hustcc', addr: 'Hangzhou', birth: '1992-08-01', // ...
// 很多的資料不列出了
};
// 結構和 spread 寫法建立 userBconst userB = {
...userA, id: 2, name: 'ProtoTeam',
};
複製程式碼
- 陣列 immutable 操作
const arr = [1, 2, 3, 4, 5];
// push 操作const arrPush = [ ...arr, 6,];
// shift 操作const [e, ...arrShift] = arr;
// concatconst arrConcat = [...arr, ...arr];
複製程式碼
四、最後
在 react + redux 技術棧下,不可變資料結構是大家都提倡的做法,所以又很多框架層面的東西幫我們處理了這些事情。
但是實際上,我們在平時的業務程式碼開發中,就需要有 immutable 的意識,善用 ES6 語法,就可以解決我們大部分的場景。
對我們團隊感興趣的可以關注專欄,關注github或者傳送簡歷至’tao.qit####alibaba-inc.com’.replace(‘####’, ‘@’),歡迎有志之士加入~
來源:https://juejin.im/post/5aa8ae316fb9a028bd4c0202?utm_medium=fe&utm_source=weixinqun