iT邦幫忙

2022 iThome 鐵人賽

DAY 11
1
Modern Web

致 JavaScript 開發者的 Functional Programming 新手指南系列 第 11

Day 11 :何謂 Immutable Data?(3):更新陣列

  • 分享至 

  • xImage
  •  

在前面的章節中,我們提到如何透過 Immutable 的方式來更新物件的值,那我們又該如何透過 Immtable 的方式來更新陣列的資料呢?

在切入到重點以前,我們可以先思考陣列的資料在前端開發中,可能會用來解決什麼問題?

答案可能是:透過迴圈處理陣列,解決重複性的任務、方便計算資料長度⋯⋯等,舉例來說,某個陣列的屬性,在某些狀況下要被抽取出來乘上某個倍數,在其他狀況下又要乘上其他的倍數(這個在某些後台分析、排序系統中,是非常常見的狀況)。

但透過前面章節的討論,我們知道陣列也是一種物件,會帶有共用參考物件的特性,更別提大部分的資料結構可能是陣列包裹著物件,物件中又包裹著其他複雜的資料結構,稍有不慎就會修改到其他的資料。

這也是為什麼我們必須了解,如何透過 Immutable 的方式,來更新、處理陣列資料。

Mutable 方式更新陣列值

在了解如何透過 Immutable 的方式來處理陣列資料前,我們同樣要來看看那些不恰當的資料整理方式,在之前的範例中,我們已經看過了下方這個範例:

const apiPath3 = "xxx";
let studentOnDutyList = [];

axios.get(apiPath3).then(({ data }) => {
  studentOnDutyList = data;
  studentOnDutyList.forEach(({name, classNum, number}) => console.log(`我是 ${classNum} 班的${name},學號是${number}`));
	let newStudentOnDutyList = studentOnDutyList.map((data) => data.number+=1);
	newStudentOnDutyList.forEach((number) => console.log(`學號:${number}`));
});
// output 1 
//我是 1 班的王小花,學號是 110110
//我是 2 班的謝小明,學號是 110210
//我是 3 班的廖小寶,學號是 110310
// output 2
//學號:110111
//學號:110211
//學號:110311

我們想要透過 API 回傳的資料進行多重的處理,但由於傳參考的特性第六行的 data.number+=1 會意外修改到原本的陣列資料。

雖然新的資料有如預期中被 map 方法回傳,但卻在我們我們針對物件內容作更改時產生了 Mutation 的效果,由於 Mutation 會在未來的某一刻造成物件狀態的污染,這不會是我們想要的結果。

讓我們來看另外一個範例:

// 假設這已經是我們透過 API 拿回來的資料
const list = [
	{
		name: '小明',
		grade: 5,
	},
	{
		name: '小可',
		grade: 1,
	},
	{
		name: '小櫻',
		grade: 3,
	},
];
const newList = list;

// 清空第零筆資料
newList[0] = {};
// 新增最新一筆的資料
newList[3] = {
		name: '小兔',
		grade: 4,
};

透過中括號加上陣列索引的方式直接改動陣列的資料,同樣也會直接操作到參考物件,所以也是一種 Mutable 的操作方式,應該盡量避免。

但經過之前的討論,我們知道透過中括號加上陣列索引的方式直接改動陣列的資料的狀況,並不是這個方法本身的問題,而是我們透過了 const newList = list; 這段語法,讓兩個陣列的參考物件被共用了。

除了中括號外,原生 JS 有提供一些既有的陣列方法,但實際上這些陣列方法也是透過 Mutable 的方式進行陣列的操作,舉例來說:

// 假設這已經是我們透過 API 拿回來的資料
const list = [
	{
		name: '小明',
		grade: 5,
	},
	{
		name: '小可',
		grade: 1,
	},
	{
		name: '小櫻',
		grade: 3,
	},
];
const newList = list;

// 刪除最後一筆資料
newList.pop();
// 新增最新一筆的資料
newList.push({
		name: '小兔',
		grade: 4,
});

此時我們會發現透過 poppush 陣列方法處理 newList 時,list 陣列因為傳參考特性也被修改到了。

除了 poppush 外,還有其他像是 sliceshiftunshiftreversesort 等方法,都會直接對物件中的參考物件進行修改。

那我們究竟要怎麼透過 Immutable 的方式來更新陣列的資料呢?

Immutable 方式更新陣列值

我們可以透過 mapfilter 方法來做到使用 Immutable 的方式操作陣列資料結構,由於會透過傳入的 callback function 回傳一組新的陣列,所以並不會影響到原陣列資料。

但要注意的是,要避免直接對原始陣列之資料進行會產生副作用的動作,例如:重新賦予變數值。

如果我們要進行資料的處理的話,可以透過 map 方法:

// 假設這已經是我們透過 API 拿回來的資料
const list = [
	{
		name: '小明',
		grade: 5,
	},
	{
		name: '小可',
		grade: 1,
	},
	{
		name: '小櫻',
		grade: 3,
	},
];
/*請特別留意這邊 callback function 中針對回傳值的處理
	處理 grade 屬性時並不是 item.grade+=1 而是利用 item.grade+1 的回傳值
	不然透過 item.grade+=1  還是會依照傳參考的特性而修改到原陣列的參考物件
*/
const newList = list.map((item) => ({...item, grade: item.grade+1}));
console.log(`list: ${list}`);
/* [
    {
        "name": "小明",
        "grade": 5
    },
    {
        "name": "小可",
        "grade": 1
    },
    {
        "name": "小櫻",
        "grade": 3
    }
] */
console.log(`newList: ${newList}`);
/*[
    {
        "name": "小明",
        "grade": 6
    },
    {
        "name": "小可",
        "grade": 2
    },
    {
        "name": "小櫻",
        "grade": 4
    }
]*/

此時我們會發現,在不更動原始陣列的狀況下,我們成功透過 map 獲得一組 grade 屬性都加上一的新陣列,在 map 的 callback function 的回傳值中,我們還利用了前面章節所聊過的展開運算子,進行物件的淺層拷貝,做到快速複製物件結構的動作。

如果這時候我們額外再針對 newList 陣列進行 mutable 的操作:

newList.pop()
console.log(`list: ${list}`);
/* [
    {
        "name": "小明",
        "grade": 5
    },
    {
        "name": "小可",
        "grade": 1
    },
    {
        "name": "小櫻",
        "grade": 3
    }
] */

會發現此時 list 並不受到影響,我們成功透過 map 使用 Immtable 的方式更新物件了!

除了進行重複性的處理,我們有時候可能會想要篩選出特定的資料群體,此時就可以透過 filter 陣列方法做到:

const list = [
	{
		name: '小明',
		grade: 5,
	},
	{
		name: '小可',
		grade: 1,
	},
	{
		name: '小櫻',
		grade: 3,
	},
];
/*請特別留意這邊 callback function 中針對回傳值的處理
	處理 grade 屬性時並不是 item.grade+=1 而是利用 item.grade+1 的回傳值
	不然透過 item.grade+=1  還是會依照傳參考的特性而修改到原陣列的參考物件
*/
const newList = list.filter(({grade}) => grade >1);
console.log(`list: ${list}`);
/* [
    {
        "name": "小明",
        "grade": 5
    },
    {
        "name": "小可",
        "grade": 1
    },
    {
        "name": "小櫻",
        "grade": 3
    }
] */
console.log(`newList: ${newList}`);
/*[
    {
        "name": "小明",
        "grade": 5
    },
    {
        "name": "小櫻",
        "grade": 3
    }
]
*/

我們可以透過在 filter 方法的回傳值,帶入我們想要篩選的條件,即可獲得符合此條件、新的陣列資料,在更進階一點地說,filter 方法也是把我們所不想要的資料給刪掉了!

在這個章節中,我們花了很多篇幅更深入地探討物件與陣列傳參考特性與 Immutable Data 的關聯性,下個章節我們簡單複習 Immutable Data 的概念與說明為什麼我們要了解 Immtable 概念及其重要性。

參考資料:

  1. Functional Programming For Beginners With JavaScript
  2. All about Immutable Arrays and Objects in JavaScript

上一篇
Day 10 :何謂 Immutable Data?(2):更新物件
下一篇
Day 12 :何謂 Immutable Data?(4):結語
系列文
致 JavaScript 開發者的 Functional Programming 新手指南30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言