善用語言特性與 Higher Order Function

if elseswitch case 是最基本的邏輯判斷方式,但卻也是 複雜度 的元兇,實務上善用 ECMAScript 語言特性與 Higher Order Function,可以降低複雜度,讓程式碼可讀性更高,也更容易維護。

Version


ECMAScript 5
ECMAScript 2015

&&


1
2
3
4
5
6
7
8
9
const func = product => {
if (product === 'iPhone')
return product;
else
return false;
};

console.log(func());
console.log(func('iPhone'));

若為 true 則直接回傳,否則回傳 false

complex015

1
2
3
4
const func = product => product === 'iPhone' && product;

console.log(func());
console.log(func('iPhone'));

可使用 &&,若為 false 直接回傳 false,若為 true 才會繼續執行 && 右側部分。

complex016

||


1
2
3
4
5
6
7
8
9
10
const func = product => {
if (product === 'iPhone')
return true;
else
return product;
};

console.log(func());
console.log(func('iPad'));
console.log(func('iPhone'));

若需求反過來,只有 false 直接回傳,否則回傳 true

complex017

1
2
3
4
5
const func = product => product === 'iPhone' || product;

console.log(func());
console.log(func('iPad'));
console.log(func('iPhone'));

可使用 ||,若為 true 直接回傳 true,若為 false 才會繼續執行 || 右側部分。

complex018

Array.includes()


1
2
3
4
5
6
7
const func = product => {
if (product === 'iPhone' || product === 'iPad' || product === 'Apple Watch' || product === 'Macbook') {
return true;
}
};

console.log(func('Apple Watch'));

常見的需求,|| 若其中一個條件成立就回傳值。

complex000

1
2
3
4
5
6
7
8
9
10
const apples = [
'iPhone',
'iPad',
'Apple Watch',
'Macbook',
];

const func = product => apples.includes(product);

console.log(func('Apple Watch'));

Array.prototype.includes() 的原意是判斷 item 是否在 array 中,若有則回傳 true,否則傳回 false

利用 includes() 這個特性,可將所有要判斷的項目改寫在 array 中,改用 includes() 判斷,不只清爽,可讀性也很高。

complex001

Guard Clause


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
const apples = [
'iPhone',
'iPad',
'Apple Watch',
'Macbook',
];

const func = (product, quantity) => {
if (product) {
if (apples.includes(product)) {
console.log(`${product} is Apple product`);

if (quantity > 10) {
console.log('big quantity');
}
}
} else {
throw new Error('No Apple product');
}
};

func();
func('Macbook');
func('iPad', 20);

若都使用 正向判斷,可能會造成 nested if else 而難以維護。

complex002

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const apples = [
'iPhone',
'iPad',
'Apple Watch',
'Macbook',
];

const func = (product, quantity) => {
if (!product) throw new Error('No Apple product');

if (!apples.includes(product)) return;
console.log(`${product} is Apple product`);

if (quantity > 10) console.log('big quantity');
};

func();
func('Macbook');
func('iPad', 20);

可使用 Guard Clause 將反向邏輯提早 return,讓所有 if 判斷都扁平化只有一層,這也是為什麼有些 Lint 會警告你不能寫 else,因為使用 Guard Clause 之後,就不會再出現 else 了。

complex003

Array.every()


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const apples = [
{ name: 'iPhone', color: 'white' },
{ name: 'iPad', color: 'black' },
{ name: 'Macbook', color: 'silver' }
];

const func = color => {
let isSame = true;

for (let product of apples) {
if (!isSame) break;
isSame = product.color === color;
}

return isSame;
};

console.log(func('white'));

若要 全部條件 都成立,實務上我們也會將資料與條件全部先放在 array 中。

最直覺方式就是透過 for loop 判斷。

complex008

1
2
3
4
5
6
7
8
9
const apples = [
{ name: 'iPhone', color: 'white' },
{ name: 'iPad', color: 'black' },
{ name: 'Macbook', color: 'silver' }
];

const func = color => apples.every(x => x.color === color);

console.log(func('white'));

若要 全部條件 都成立,可使用 Array.prototype.every(),則所有條件都為 true 才會回傳 true

complex009

Array.some()


1
2
3
4
5
6
7
8
9
const apples = [
{ name: 'iPhone', color: 'white' },
{ name: 'iPad', color: 'black' },
{ name: 'Macbook', color: 'silver' }
];

const func = color => apples.some(x => x.color === color);

console.log(func('white'));

若只要 有一個條件 成立即可,可使用 Array.prototype.some()

complex010

Default Parameter


1
2
3
4
5
6
7
8
9
10
11
const func = (product, quantity) => {
if (!product) return;

quantity = quantity || 1;

return `We have ${quantity} ${product}!`;
};

console.log(func());
console.log(func('iPad'));
console.log(func('iPhone', 2));

ES5 function 並沒有提供 Default Parameter,因此會透過判斷 parameter 是否為 undefined|| 的小技巧設定預設值。

但由於 0''false 也視為 Falsy Value,若你的需求是能接受 0''false,就不能使用這種技巧

complex004

1
2
3
4
5
6
7
8
9
const func = (product, quantity = 1) => {
if (!product) return;

return `We have ${quantity} ${product}!`;
};

console.log(func());
console.log(func('iPad'));
console.log(func('iPhone', 2));

ECMAScript 2015 提供了 Default Parameter 之後,語意更加清楚,也不用判斷 undefined 了。

complex005

Object Destructing


1
2
3
4
5
6
7
8
9
10
11
const func = product => {
if (product && product.name) {
return product.name;
} else {
return 'unknown';
}
};

console.log(func());
console.log(func({}));
console.log(func({ name: 'iPhone', color: 'white' }));

若 parameter 為 object,在 ES5 為避免 parameter 為 undefinednull,又避免根本無 property,必須小心翼翼的判斷 object 與 property。

complex006

1
2
3
4
5
const func = ({ name } = {}) => name || 'unknown';

console.log(func());
console.log(func({}));
console.log(func({ name: 'iPhone', color: 'white' }));

透過 ECMAScript 2015 的 Default Parameter 與 Object Destructing,可一次解決判斷 object 與 property 問題。

  • 當 parameter 為 undefined 時,會使用預設值 {}
  • 當 object 沒有 property 時, Object Destructing 拆解後為 undefined,都是 unknown
  • 否則會正常取得 property 值

complex007

Object Literal


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const func = color => {
switch (color) {
case 'white':
return ['iPhone', 'iPad'];
case 'silver':
return ['Macbook', 'Apple Watch'];
case 'black':
return ['Apple TV', 'Mac Pro'];
default:
return [];
}
};

console.log(func());
console.log(func('white'));

switch case 在實務上也難以避免。

complex011

1
2
3
4
5
6
7
8
9
10
const apples = {
white: ['iPhone', 'iPad'],
silver: ['Macbook', 'Apple Watch'],
black: ['Apple TV', 'Mac Pro'],
};

const func = color => apples[color] || [];

console.log(func());
console.log(func('white'));

由於 object 本身就是 key / value,因此可以把 object 當 Dictionary 使用取代 switch case

Default 值就使用 || 表示。

complex012

Map


1
2
3
4
5
6
7
8
9
const apples = new Map()
.set('white', ['iPhone', 'iPad'])
.set('silver', ['Macbook', 'Apple Watch'])
.set('black', ['Apple TV', 'Mac Pro']);

const func = color => apples.get(color) || [];

console.log(func());
console.log(func('white'));

ECMAScript 2015 提供了新的 Map,想法類似 object。

complex013

Array


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const apples = [
{name: 'iPhone', color: 'white'},
{name: 'iPad', color: 'white'},
{name: 'Macbook', color: 'silver'},
{name: 'Apple Watch', color: 'silver'},
{name: 'Apple TV', color: 'black'},
{name: 'Mac Pro', color: 'black'},
];

const func = color =>
apples
.filter(x => x.color === color)
.map(x => x.name);

console.log(func());
console.log(func('white'));

也可以把所有條件都放進 array,改用 Array.prototype 下豐富的 method。

complex014

Conclusion


  • 並不是所有的判斷都只能用 if elseswitch case,透過一些 ECMAScript 的語言特性與 Higher Order Function,可以有效降低程式碼複雜度

Reference


Jecelyn Yeen, 5 Tips to Write Better Conditionals in JavaScript