今天來討論另一個容易被忽略的主題,如果要表達「有值」的情況,大家都很熟悉:
const score = 95;
const name = 'Joey';
const arr = ['Jack', 'Allen'];
const person = { name: 'Joey', age: 20 };
const isOpened = true;
但如果遇上「無值」,或者「未知」的情況,很容易會遇到以下這幾個:
undefined、null、NaN
讓我們今天一個一個來剖析它們吧!
直接翻譯叫做「尚未定義」的變數,也就是像這樣:
let name;
console.log(name); // undefined
聽起來好像只要經過賦值,就回不去 undefined
了嗎?其實還是可以的,來源於全域物件的 undefined
:
let name = 'Joey';
console.log(name); // Joey
name = undefined;
console.log(name); // undefined
所以,undefined
不是一種「狀態」,它就跟字串、數字、陣列一樣,undefined
就是一種「值」,只是它的值叫做 undefined。
同時它的 type 也是很特別的,就叫做 undefined
:
console.log(typeof undefined); // "undefined"
而這個 undefined
會在變數初始化時,如果沒有一開始就賦值,那 Javascript 就是直接給它一個 undefined
的值。
因此,原本我們會說「某個變數沒有初始值」,但其實,只要你有用 let
或 var
去宣告它,它就一定有初始值:
let name;
// 變數只要經過宣告,就會有個 undefined 的值
console.log(name); // undefined
// 變數沒有宣告就使用會產生 Reference Error
console.log(name2); // Uncaught ReferenceError: name2 is not defined
大部分的實際情況是,如果程式都是自己寫的,基本上變數是不是 undefined
自己都很清楚。
但現在經常去使用第三方的套件,或者是自己串接他人的後端,往往你也不知道對方到底傳了什麼過來,最基本的判斷就是先確保不是 undefined
。
尤其如果你寫的函式要給很多人呼叫,就更要在函式最開頭先做基本的 validation,像這個有瑕疵的版本:
const displayName = (firstName, lastName) => {
return `${firstName} ${lastName}`;
};
displayName('Joey'); // "Joey undefined"
可以改成:
const displayName = (firstName, lastName) => {
const nameAry = [];
if (typeof firstName !== 'undefined') nameAry.push(firstName);
if (typeof lastName !== 'undefined') nameAry.push(lastName);
return nameAry.join(' ');
};
displayName('Joey'); // "Joey"
css 要設定 style 的時候,會遇到「if 某種情況,要有這個 style,else 就維持原樣」,如果用 if/else 來寫會這樣:
const isRedColor = true;
let color;
if (isRedColor) {
color = 'red';
}
// elem 是模擬一個 DOM 元素
elem.style.color = color;
但其實上面這段的第 2 行,還記得嗎?其實就等於:
let color = undefined;
所以改用三元運算子會輕鬆一點,而且 color
可以用 const
來宣告,其實比較符合這個變數的原意(初始化之後就沒有要改了)。
const isRedColor = true;
const color = isRedColor ? 'red' : undefined;
// elem 是模擬一個 DOM 元素
elem.style.color = color;
上面提過 undefined
也是一種「值」,所以 Object 裡面也可以放 undefined
:
const person = {
name: 'Joey',
height: 173,
weight: 63,
son: undefined
};
但還記得我們在 Day 4 - Object 提到過,多餘的 property 很容易在執行 Object.keys
系列的函式時,出現意想不到的狀況:
const person = {
name: 'Joey',
height: 173,
weight: 63,
son: undefined
};
Object.entries(person)
.forEach(([key, value]) => `${key}:${value}`);
執行結果
name:'Joey'
height:173
weight:63
son:undefined
null
是一種值,它的意思是「故意地沒有值」。好吧我知道大家看不懂我在寫什麼,所以如果看原文應該比較好理解:
The value null represents the intentional absence of any object value.
簡單說,就是這個變數有宣告而且有值,而它的值是「空值」的概念。
拿來跟前面提到的 undefined
比較一下:
let name;
console.log(name); // undefined
const nullName = null;
console.log(nullName); // null
console.log(typeof name); // "undefined"
console.log(typeof nullName); // "object"
我們可以用比較白話的方式來解釋這段 code:
我宣告了一個變數叫做
name
,但這個變數我還沒想到要給它什麼值
我宣告了一個變數叫做nullName
,這個變數我決定讓它代表空值
所以,即便這兩個變數都可以說是「什麼都沒有」,但比較細微的差距在於,開發者有沒有「意圖」要定義這個變數:
undefined
null
另一個有趣的點是,透過 typeof
取得的值:
console.log(typeof name); // "undefined"
console.log(typeof nullName); // "object"
這是一個很神奇的設計,有 undefined
這個類別,但沒有 null
這個類別,事實上,就連 MDN 都說這是
bug in ECMAScript
所以現階段如果要判斷是不是 null
,可以單純就用嚴格相等:
console.log(nullName === null); // true
DB 要 update data 時,如果這個欄位「沒有變動」,通常會放 undefined
或直接就不放,但如果要強調這個欄位叫做「空值」,則應該要放 null
。
比如一個原本又瘦又有車的 Joey,變成又胖又沒車的 Joey:
// 假設 DB 目前有這筆資料:
// {
// id: '61226502e1c26332bcb5f9ca',
// name: 'Joey',
// weight: 63,
// car: 'TOYOTA'
// }
const updateObject = {
id: undefined, // 這行可以移除
name: undefined, // 這行可以移除
weight: 73,
car: null
};
updateById('61226502e1c26332bcb5f9ca', updateObject);
// 更新完後可能會是這樣
// {
// id: '61226502e1c26332bcb5f9ca',
// name: 'Joey',
// weight: 73,
// car: null
// }
今天介紹的東西真的一個比一個奇葩,這位選手叫做「不是個數字」。
NaN:Not-A-Number
通常是在 Math 函式計算失敗(如:Math.sqrt(-1)
)或函式解析數字失敗(如:parseInt("blabla")
)後才會回傳:
console.log(Math.sqrt(-1)); // NaN
console.log(parseInt("blabla")); // NaN
奇特的是,它雖然「不是個數字」,但如果印出它的 type:
console.log(typeof NaN); // "number"
沒錯,「不是個數字」的類別是「數字」。
而且如果它不像 null
可以用嚴格相等來判斷出來:
console.log(null === null); // true
console.log(NaN === NaN); // false
console.log(parseInt("blabla") === NaN); // false
只能夠使用 Number.isNaN()
或 isNaN()
來判斷,我個人比較習慣用前者,比較單純一點,想知道差異可以看 MDN:
console.log(Number.isNaN(NaN)); // true
console.log(Number.isNaN(parseInt("blabla"))); // true
由於它跟 number
相關,所以我個人最常用到的時機點就是 string to number 的時候,比如網址上的參數:
const url = 'https://example.com?score=8';
// 拆 url 的 query 參數沒這麼簡單,這邊是偷吃步
const score = url.split('=')[1];
const scoreNum = parseInt(score, 10);
console.log(scoreNum * 3); // 24
我們沒有辦法預期網址上的 score
是不是真的都能夠被 parseInt
解析,比方說這個來亂的:
const url = 'https://example.com?score=八';
// 中間都跟上面一樣
console.log(scoreNum * 3); // NaN
這時就還是要透過特別的判斷式,以確保是一個可以被進行數學運算的 number
:
const url = 'https://example.com?score=八';
// 中間都跟上面一樣
if (Number.isNaN(scoreNum)) {
console.log('score 請帶入數字');
} else {
console.log(scoreNum * 3);
}
執行結果
score 請帶入數字
今天介紹了 undefined
、null
、NaN
三個「非主流」,除了比較細微的差異,也帶到了常常會碰到它們的地方。
很多時候看到有些人會想用 「!
」 這個運算子,來一次排除掉這三種空值的概念:
const a = undefined;
const b = null;
const c = NaN;
if (!a && !b && !c) {
console.log('用一個驚嘆號似乎就可以判斷了?');
}
但會衍生出什麼問題呢?我們留到明天來探討囉!
周旋在這個世界
你害怕的
是未知
還是一無所有?