意象說明
Truthy? Falsy? < 迷惘
簡介
在 JavaScript 中,有許多數值,在邏輯判斷中,其結果與 false
等價。由於其數值實際上並非 false
,因此,特別稱此類數值為 falsy value。
falsy values
Value | Type | Implementation |
---|---|---|
0 | Number | Constant |
NaN | Number | Global Variable |
'' | String | Constant |
false | Boolean | Constant |
null | Object | Constant |
undefined | Undefined | Global Variable |
基本邏輯判斷方法
下面使用 if
敘述直接進行 truthy / falsy 判斷,上表中所有的 falsy 值皆無法通過 if
敘述的判斷,而其餘的值皆會通過:
1 | if (value) { |
等同於:
1 | value ? true : false |
下面這個例子比較特別,注意 ===
運算子不會進行型別轉換,所以唯一會通過邏輯判斷的情況,是 value 的值恰好為 true
的情況,其餘的所有值,結果都是 false
。
1 | value === true |
下面這個例子,由於 ==
運算子會進行型別轉換,上表中所有的 falsy 值,都將先轉換為 false
,然後才與 true
進行比較,結果當然為 false
:
1 | value == true |
Falsy 值測試範例:
1 | var falsy = [0, NaN, '', false, null, undefined]; |
判斷 string 值的方法
要判斷字串是否等於特定值,毫無疑問,可以使用 ===
運算子直接進行判斷:
1 | if (title === 'untitled') { |
然而,需要特別注意的是,''
(空字串) 也是 falsy。
如果程式不接受空字串,也就是說,可以把空字串直接當作是未定義值,那麼可以直接進行邏輯判斷:
下面的例子,假設 title
是字串,且唯有當 title
字串長度大於 0
時,才進行處理:
1 | if (title) { |
或者,當 title
未給定值,或者是空字串時,則給予預設值:
1 | setTitle(title || DEFAULT_VALUE); |
但是,若空字串也是有效值,則反而需要特別的處理。問題是,該如何判斷字串有給值 (包含空字串也算有值)?
以下皆是不佳甚至錯誤的處理方式:
1 | // title 可能為 null |
比較好的方法,是利用 typeof
輔助判斷,直接檢查該值的型態是否為 string
:
1 | typeof value === 'string' // 有給值,至少是空字串 |
譬如:
1 | setTitle(typeof title === 'string' ? title : DEFAULT_VALUE); |
判斷 number 值的方法
由於 0
也是 falsy,因此有著與字串類似的問題:
不允許 0
的情況,可以直接進行邏輯判斷:
1 | if (value) { |
允許 0
的情況,則建議利用 typeof
輔助判斷,直接檢查該值的型態是否為 number
:
1 | setValue(typeof value === 'number' ? value : DEFAULT_VALUE); |
判斷物件屬性是否存在的方法
判斷物件的屬性值,方法與上述的一般數值相同,當然也包括對於字串和數值的 falsy 值的特別處理。
如果要判斷物件是否具有某個特定的屬性的話,則又有以下不同的方式。假設要判斷 object
物件中是否具有 property
這個屬性,以下都是可行的方式:
1 | object.property === undefined |
然而,最好的方式,是使用 in
運算子:
1 | if ('property' in object) { |
如果需要忽略繼承的屬性的話,可以使用 .hasOwnProperty()
函數:
1 | if (object.hasOwnProperty('property')) { |
undefined
與 null
,使用時機?
除了 falsy 值容易造成困擾,undefined
與 null
也是另一個容易讓人不知所措的特性。
上面的例子已經可以看到,對 undefined
與 null
直接進行邏輯判斷時,兩者皆表現為 falsy:
1 | var value1; // undefined |
而要加以區別的話,可以如下處理:
1 | // 判斷是否為 undefined |
關於兩者的使用時機:
undefined
如同其字面上的意義:『未定義』,應該使用在可以省略,或是可以略過定義的場合
通常使用時機如下:
- 物件的可選屬性,
- 函數的可選參數,以及
- 函數的區域變數,在特定流程下也許不會用到的變數。
由於通常我們只關心其是否定義,並且 falsy 的狀況剛好可以當作未定義來處理,因此可以直接進行邏輯判斷,即使實際上該值可能為 null
也無妨:
範例
1 | function post(url, data, options) { |
null
則應該用於明確表達『不具有值』的情況。
譬如資料庫在儲存欄位的時候,必須明確表示該欄位是否具有值。
1 | employee.save({ |
再舉例,node.js 的 callback 函數,依慣例其形式為 callback(err, result)
。
其中 err
是用來標示錯誤的發生,當其值不為 falsy 時,表示發生錯誤,而 err
通常就是錯誤訊息或存放錯誤訊息的物件。
相反地,當 err
為 falsy 時 (依慣例通常是 null
,以表示:沒有 error),這時候,result
用來表示回傳的結果。
1 | function read(file, callback) { |
總結:一般原則
若需要檢查是否給值,而不論其 falsy 狀態,則直接使用
typeof
對目標型別進行確認:1
2
3
4
5
6
7
8
9if (typeof title === 'string') {
this.title = title;
}
if (typeof options.zIndex === 'number') {
this.zIndex = options.zIndex;
}
if (typeof callback === 'function') {
callback(null, result);
}若 falsy 狀態恰巧是期望的行為,則直接進行邏輯判斷:
1
2
3
4
5options = options || {};
this.title = title || DEFAULT_VALUE;
if (message) {
console.log(message);
}
結論
由於 JavaScript 對於 falsy 值的認定過於寬鬆,甚至比 C/C++ 還寬鬆,導致在處理有效值的時候,反而需要進行額外的處理。這當然是 bad parts,而且還是無法避開的部份。希望經過本文的詳細探討之後,能幫助初學者掌握到其中的關鍵。
歡迎大家的回饋與心得分享。