前言
今天来讨论另一个容易被忽略的主题,如果要表达「有值」的情况,大家都很熟悉:
const score = 95;
const name = 'Joey';
const arr = ['Jack', 'Allen'];
const person = { name: 'Joey', age: 20 };
const isOpened = true;
但如果遇上「无值」,或者「未知」的情况,很容易会遇到以下这几个:
undefined、null、NaN
让我们今天一个一个来剖析它们吧!
undefined是「值」
直接翻译叫做「尚未定义」的变数,也就是像这样:
let name;
console.log(name); // undefined
听起来好像只要经过赋值,就回不去 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
自己都很清楚。
但现在经常去使用第三方的套件,或者是自己串接他人的后端,往往你也不知道对方到底传了什么过来,最基本的判断就是先确保不是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 也可以用到 undefined
css 要设定 style 的时候,会遇到「if 某种情况,要有这个 style,else 就维持原样」,如果用 if/else 来写会这样:
const isRedColor = true;
let color;
if (isRedColor) {
color = 'red';
}
elem.style.color = color;
但其实上面这段的第 2 行,还记得吗?其实就等于:
let color = undefined;
所以改用三元运算子会轻松一点,而且 color 可以用 const 来宣告,其实比较符合这个变数的原意 (初始化之后就没有要改了)。
const isRedColor = true;
const color = isRedColor ? 'red' : undefined;
elem.style.color = color;
注意 Object 里面的 undefined
上面提过 undefined 也是一种「值」,所以 Object 里面也可以放undefined: 还记得我们在彻底掌握 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 null是一种值,它的意思是「故意地没有值」。好吧我知道大家看不懂我在写什么,所以如果看原文应该比较好理解:
The value null represents the intentional absence of any object value.
简单说,就是这个变数有宣告而且有值,而它的值是「空值」的概念。
null 与 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 存 null 值
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
今天介绍的东西真的一个比一个奇葩,这位选手叫做「不是个数字」。
NaN:Not-A-Number
通常是在 Math 函式计算失败(如:Math.sqrt(-1)
)或函式解析数字失败(如:parseInt("blabla")
)后才会回传:
console.log(Math.sqrt(-1)); // NaN
console.log(parseInt("blabla")); // NaN
这些 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
parse 成 number 时要特别小心
由于它跟 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 请带入数字