iT邦幫忙

第 11 屆 iT 邦幫忙鐵人賽

DAY 3
1
Modern Web

深入現代前端開發系列 第 3

Day3 [JavaScript 基礎] JavaScript 基本陣列操作

Javascript 常見操作簡介

由於篇幅的關係,這裡只介紹常見的陣列操作。

陣列的操作

map filter reduce every some

為什麼陣列的操作那麼重要?雖然沒有這些方法你也可以做開發,以及做到完全一樣的事情,但是這些高階操作能夠幫助你、以及你的夥伴更容易清楚你的程式碼在做什麼。

最主要的差別在於聲明式與命令式。舉例來說,如果我們想要反轉一個字串:

// 命令式
var str = "abcd";
var result = "";
for (let i = str.length -1; i >= 0; i--) {
  result += str[i];
}

// 聲明式
"abcd".reverse();

可以發現幾件事情:

  • 與命令式比起來,少了變數宣告
  • 與命令式比起來,少了 for...loop 迴圈

要知道在寫程式的時候最討厭 for...loop 的變數條件用錯,或是一個不小心讓迴圈的變數變成全域變數

這看起來有點像...把實作包裝成函數?
可以這麼說,但怎麼設計這個函數,讓他提供足夠的抽象,同時又能具備一定程度的彈性,是在寫聲明式時要注意的事情。

這樣寫可以更有效幫助我們除錯,也更容易表達出這段程式碼想要做什麼事情。或許有人會懷疑會不會讓效能變差,不過除非你的程式已經到需要這樣斤斤計較的地步,不然犧牲一點點的效能,可以換來易讀性更高的程式碼。

以下介紹一些陣列當中常見的操作:

map(function(val, index, arr))

能夠將陣列轉換成新的陣列。

基本範例:

// 將數字 * 2
const arr = [1,2,3,4];
arr.map(val => val * 2);
function multiply(n) {
  return (val) => {
    return val * n;
  }
}
arr.map(multiply(2));

// 轉換 data field
const data = [
  { Name: 'kalan', Age: 22, Introduction: { Brief: 'xxxx', Details: 'longlongtext'}},
    { Name: 'kalan2', Age: 22, Introduction: { Brief: 'xxxx2', Details: 'longlongtext2'}}
];

data.map(user => {
  return {
    name: user.Name,
    age: user.Age,
    introduction: user.Introduction.Brief,
  };
});

我們看到第一個案例,簡單地將所有數字 * 2。另外一個比較實用的案例是,我們將回傳的資料先轉換成小寫,並取出我們想要的欄位,轉換後的資料比較容易拿來使用。比起用 for(let i = 0; i < arr.length; i++) 程式碼更簡潔,程式碼要表達的意圖也相當明顯。

除非你在做演算法相關的試題或應用,用迴圈比較好處理,或是你正在拼命榨出效能,不然處理資料的部分通常可以用 map 等高階操作搞定。

警告!!!

特別要注意的是,因為這個函數大家期待的是一個新的 array,所以強烈建議不要在裡頭做任何 side effect 的操作,像是 console.log,呼叫 API,更改 DOM 等等,因為大家並不預期裡頭會有這樣的操作,就算你相當清楚你在做什麼,過了一個禮拜後你還是有可能忘記,並且埋下了一個或許不好發現的 bug。

filter

說起來 filter 這個 API 命名其實有點不明確,因為有時常常會想,到底是把符合條件的值 filter 掉,還是把符合條件的值留下來?

在做資料查詢,或是你想要根據某個條件過濾值的時候相當好用:

const arr = [1,2,3,4,5];
arr.filter(val => val % 2 === 0); // [2,4];

// 挑出已成年的人
const data = [
  { id: 1, age: 18 },
  { id: 2, age: 20 },
  { id: 3, age: 14 },
];

data.filter(val => val.age >= 18);

reduce(function(accumalator, current, index, array), initialValue)

reduce 的妙用相當多,最常見的就是將陣列組合成一個值,例如計算總和:

const arr = [1,2,3,4,5];
arr.reduce((acc, curr) => {
  acc += curr;
  return acc;
}, 0)

或是把陣列全部塞到物件裡頭:

const arr = [
  {id: 'a', name: 'wu' },
  {id: 'b', name: 'ka'},
  {id: 'c', name: 'jack'},
];
const result = arr.reduce((acc, curr) => {
  return Object.assign(acc, {
    [curr.id]: curr
  });
}, {});
console.log(result)
// {a: {id: "a", name: "wu"}
// b: {id: "b", name: "ka"}
// c: {id: "c", name: "jack"}}

every(function(val, index))

如同它的名字 every 一樣,會根據回傳值,來判定只有當陣列全部的值都為 true 才回傳 true。例如我想要這個陣列裡頭全部都是 18 歲以上的人,就可以這樣寫:

const people = [
  { age: 18 },
  { age: 20 },
  { age: 22 },  
];

const isAllAdult = people.every(p => p.age >= 18); // true

some

和 every 類似的函數,不過只要其中一個元素符合傳入的函式就會回傳 true。

const messages = [
  'hello',
  'hello world',
  'apple'
];

const containHello = messages.some(msg => msg.includes('hello')); // true

小結

為什麼會特別介紹這些 API 是因為剛開始為了學習方便會習慣用 for loop 的方式寫,如果沒有常常去看其他人的程式碼或是和別人合作過,難免就這樣繼續寫下去了,但是在處理資料或是寫應用的時候,寫這些程式碼不只會多出好幾個變數,閱讀起來也不是那麼方便,往往要看到迴圈內的操作才能明白想要幹嘛,因此或許已經有很多人都知道,但還是花了一個章節來談論它。

除此之外,在 JavaScript 以及 ES6 語法當中,也有許多可以讓 code 看起來更簡潔的小技巧,這裡就不多贅述。

雖然 JavaScript 中有很多奇技淫巧,但正常的情況下你應該避免使用它。我們不應該為了少寫一點點的程式碼而選用一個晦澀難懂的方式。任何語言都是這樣。

進階篇 — 實作 Array.prototype.map

了解陣列的操作後,我們試著從設計 API 角度出發,實作看看 map。

根據 MDN 上的描述,一個 map 的函式簽名是這樣的:

var new_array = arr.map(function callback(currentValue[, index[, array]]) {
    // Return element for new_array
}[, thisArg])

第一個參數是一個函數,回傳的值將會成為陣列的新元素,第二個可選參數是 this,你可以綁定自己的 this。

首先要思考的是,如果用 prototype 來實作 map 這個 function,那麼 this 會是什麼?在 Array.prototype 中的話,this 就會是整個陣列:

const arr = [];
arr.map()
Array.prototype.map = function(fn, thisArg) {
  const result = [];
  for (let i = 0; i < this.length; i++) {
	  result.push(fn.call(thisArg || null), this[i], i, this));
  }
  return result;
};

一個簡單的函式實作可以看到 API 設計時可以考量的地方:

  • 透過傳入 function 將實作交給外部決定,不會讓函數過度耦合
  • 盡可能地將參數提供出來給實作者操作,像是 currentValue, index, array 等等
  • 透過 thisArg 的綁定傳入特定的 context。

在建立函數的時候也有煩惱不知道要參數化哪些地方嗎?試著從原生的 API 中思考一下吧!


上一篇
Day2 [JavaScript 基礎] 淺談 ECMAScript 與 JavaScript
下一篇
Day4 [JavaScript 基礎] 我知道 `==` 與 `===` 不同,但為什麼? 淺談相等性
系列文
深入現代前端開發32

2 則留言

0
tsuifei
iT邦新手 5 級 ‧ 2019-10-15 15:40:39

原來以原生的 JS 所寫出來的方法稱「命令式」,一直以為這種寫法可以稱為 polyfile ....
感謝出好文,受益良多~

0
hannahpun
iT邦新手 5 級 ‧ 2019-11-06 03:51:19

之前看過如何 flat 的方法,用 reduce 也相當好用啊

let array = [1, 2, [5, 6]]
array.reduce((cur, acc) => {
console.log(acc)
	return cur.concat(acc)
}, [])

我要留言

立即登入留言