前端JavaScript之NaN、undefined、null示例詳解
前言
在 JavaScript 的世界里,有三個(gè)特殊的值常常讓初學(xué)者甚至有經(jīng)驗(yàn)的開發(fā)者感到困惑:undefined、null 和 NaN。它們都在某種程度上表示 “空” 或 “無(wú)值”,但在語(yǔ)義、類型系統(tǒng)以及運(yùn)行時(shí)行為上卻有著天壤之別。
很多線上 bug 的根源,往往就在于混淆了這三者的區(qū)別。比如,你是否曾疑惑過(guò)為什么 typeof null 會(huì)返回 'object'?為什么 NaN === NaN 會(huì)返回 false?為什么 null == undefined 是 true 但 null === undefined 卻是 false?
本文將帶你深入這三個(gè)特殊值的底層邏輯,拆解它們的產(chǎn)生場(chǎng)景、類型特征以及相等性判斷的陷阱,幫助你徹底理清這三者的關(guān)系,寫出更健壯的代碼。
一、undefined:系統(tǒng)的 “未定義”
1.1 語(yǔ)義:自然的缺失
undefined 的核心語(yǔ)義是 “未定義” 。它代表的是一種 “自然的缺失”—— 也就是說(shuō),當(dāng) JavaScript 引擎在找不到值的時(shí)候,它會(huì)默認(rèn)用 undefined 來(lái)填充。
這不是開發(fā)者主動(dòng)設(shè)置的,而是語(yǔ)言本身的默認(rèn)行為。當(dāng)一個(gè)變量聲明了但還沒賦值,或者訪問(wèn)了一個(gè)對(duì)象上不存在的屬性,JavaScript 并不會(huì)拋出錯(cuò)誤,而是返回 undefined 告訴你:“這里應(yīng)該有個(gè)值,但我還沒找到。”
1.2 常見的產(chǎn)生場(chǎng)景
undefined 通常出現(xiàn)在以下幾種情況:
變量聲明但未賦值:這是最常見的場(chǎng)景。
let name; console.log(name); // undefined
訪問(wèn)不存在的對(duì)象屬性:
const user = { name: 'John' }; console.log(user.age); // undefined
函數(shù)沒有返回值:如果一個(gè)函數(shù)沒有
return語(yǔ)句,它默認(rèn)返回undefined。function doSomething() { // 沒有 return } console.log(doSomething()); // undefined
函數(shù)參數(shù)未傳參:
function greet(name) { console.log(name); // undefined } greet(); // 沒傳參數(shù)
1.3 類型與注意事項(xiàng)
undefined 本身是一個(gè)獨(dú)立的原始數(shù)據(jù)類型(Undefined Type),它只有一個(gè)值,就是 undefined。
使用 typeof 運(yùn)算符檢測(cè) undefined 時(shí),會(huì)準(zhǔn)確地返回 'undefined':
let a; console.log(typeof a); // 'undefined'
最佳實(shí)踐:不要主動(dòng)給變量賦值為
undefined。因?yàn)?undefined是語(yǔ)言用來(lái)表示 “缺失” 的默認(rèn)值,如果你手動(dòng)賦值let a = undefined,會(huì)混淆 “本來(lái)就沒有值” 和 “我故意把它清空了” 這兩種語(yǔ)義。如果你想表示清空,應(yīng)該使用null。
二、null:開發(fā)者的 “空指針”
2.1 語(yǔ)義:主動(dòng)的清空
與 undefined 相反,null 的核心語(yǔ)義是 “空值” 。它代表的是一種 “主動(dòng)的清空”。
null 意味著:“我,開發(fā)者,明確地告訴引擎,這個(gè)變量現(xiàn)在是空的,它不指向任何對(duì)象。”
這是一個(gè)有意為之的狀態(tài)。通常我們用它來(lái)表示一個(gè)變量本來(lái)應(yīng)該是一個(gè)對(duì)象,但現(xiàn)在暫時(shí)沒有值。比如,在等待異步請(qǐng)求返回?cái)?shù)據(jù)之前,我們可以把變量初始化為 null,表示 “數(shù)據(jù)還沒加載好”。
2.2 常見的使用場(chǎng)景
初始化對(duì)象變量:
// 表示用戶對(duì)象目前為空,等待后續(xù)賦值 let currentUser = null; // 登錄成功后 currentUser = { id: 1, name: 'John' };
主動(dòng)釋放引用:在一些手動(dòng)內(nèi)存管理的場(chǎng)景下,將對(duì)象引用置為
null可以幫助垃圾回收。let bigData = getBigData(); // 處理完數(shù)據(jù) bigData = null; // 釋放引用
函數(shù)返回 “無(wú)結(jié)果” :當(dāng)查詢數(shù)據(jù)庫(kù)沒有找到結(jié)果時(shí),返回
null而不是拋出錯(cuò)誤,表示 “找到了,但結(jié)果是空的”。
2.3 那個(gè)著名的 Bug:typeof null === 'object'
這是 JavaScript 中最廣為人知的歷史遺留問(wèn)題。當(dāng)你使用 typeof 檢測(cè) null 時(shí),你會(huì)得到:
console.log(typeof null); // 'object'
這其實(shí)是 JavaScript 最初實(shí)現(xiàn)時(shí)的一個(gè)錯(cuò)誤。在最初的 JavaScript 引擎中,值是由一個(gè)標(biāo)簽和實(shí)際數(shù)據(jù)表示的。對(duì)象的標(biāo)簽是 0,而 null 表示空指針,在大多數(shù)平臺(tái)下是空指針的引用也是 0x00,所以它的標(biāo)簽也被誤寫成了 0,導(dǎo)致 typeof 把它當(dāng)成了對(duì)象。
雖然這個(gè)錯(cuò)誤已經(jīng)被所有人知道了,但由于兼容性的原因,ECMAScript 標(biāo)準(zhǔn)一直沒有修復(fù)它。
因此,永遠(yuǎn)不要用 typeof 來(lái)檢測(cè) null ! 正確的檢測(cè)方式是直接使用嚴(yán)格相等:
if (value === null) {
// 這才是正確的判斷方式
}
三、NaN:數(shù)字里的 “壞孩子”
3.1 語(yǔ)義:無(wú)效的數(shù)字
NaN 全稱是 Not-a-Number,即 “非數(shù)字”。但這并不意味著它的類型不是數(shù)字。恰恰相反,NaN 是一個(gè) 數(shù)值類型 的特殊值。
它的語(yǔ)義是:“這本來(lái)應(yīng)該是一個(gè)數(shù)字,但是運(yùn)算失敗了,所以我用 NaN 來(lái)表示這個(gè)無(wú)效的結(jié)果。”
比如,你試圖把一個(gè)字符串 "abc" 轉(zhuǎn)換成數(shù)字,或者對(duì)負(fù)數(shù)開平方,JavaScript 不會(huì)拋出異常,而是返回 NaN 來(lái)告訴你:“這次數(shù)字運(yùn)算搞砸了。”
console.log(typeof NaN); // 'number' // 沒錯(cuò),它的類型是 number!
這是因?yàn)?JavaScript 遵循了 IEEE 754 浮點(diǎn)數(shù)標(biāo)準(zhǔn),而 NaN 正是該標(biāo)準(zhǔn)中定義的一個(gè)特殊數(shù)值,用來(lái)表示非法的計(jì)算結(jié)果。
3.2 最反直覺的特性:傳染性與自不等
NaN 有兩個(gè)極其特殊的性質(zhì),也是無(wú)數(shù) bug 的來(lái)源:
傳染性:只要你的數(shù)學(xué)運(yùn)算中混入了
NaN,那么最終的結(jié)果一定是NaN。它就像病毒一樣會(huì)傳染。console.log(1 + NaN); // NaN console.log(2 * NaN); // NaN console.log(Math.max(1, 2, NaN, 3)); // NaN- 這意味著,一旦你的計(jì)算鏈中某個(gè)環(huán)節(jié)出錯(cuò)產(chǎn)生了
NaN,它會(huì)一路污染到最終結(jié)果,而且很難定位到底是哪里出的錯(cuò)。
它不等于任何值,包括它自己:這是最反直覺的一點(diǎn)。
console.log(NaN === NaN); // false console.log(NaN == NaN); // false- 為什么會(huì)這樣?因?yàn)?IEEE 754 標(biāo)準(zhǔn)規(guī)定,
NaN不與任何值相等,包括它自己。這是為了讓你能通過(guò)x !== x來(lái)檢測(cè)NaN。
3.3 如何正確檢測(cè) NaN?
既然 === 不好使,那我們?cè)撛趺礄z測(cè)一個(gè)值是不是 NaN 呢?
全局的
isNaN():這是最早的方法,但它有坑。它會(huì)先把參數(shù)轉(zhuǎn)換成數(shù)字,如果轉(zhuǎn)換失敗就返回true。這意味著它會(huì)把很多非數(shù)字的值也誤判為NaN。console.log(isNaN('hello')); // true!因?yàn)?'hello' 轉(zhuǎn)數(shù)字失敗了 console.log(isNaN(undefined)); // true- 這顯然不對(duì),因?yàn)?
'hello'本身并不是NaN,它只是個(gè)字符串。
Number.isNaN():ES6 引入的正確方法。它不會(huì)做類型轉(zhuǎn)換,只有當(dāng)值真的是NaN時(shí)才返回true。console.log(Number.isNaN(NaN)); // true console.log(Number.isNaN('hello')); // false console.log(Number.isNaN(undefined)); // false
利用自不等特性:這是一個(gè)古老的 trick,因?yàn)橹挥?
NaN才會(huì)不等于自己。function myIsNaN(value) { return value !== value; }
四、一張表看懂三者的區(qū)別
為了讓你更直觀地對(duì)比這三者的區(qū)別,我們整理了一張核心特性對(duì)比表:

從表中可以清晰地看到,雖然它們?cè)诓紶栟D(zhuǎn)換中都為 false,但在類型、語(yǔ)義和轉(zhuǎn)換行為上完全不同。
五、相等性判斷的迷局
搞清楚了它們各自的定義,接下來(lái)最容易踩坑的就是相等性判斷了。JavaScript 提供了三種比較方式:==、=== 和 Object.is(),它們對(duì)這三個(gè)值的處理各不相同。
5.1 == vs ===:null 和 undefined 的曖昧關(guān)系
我們都知道 == 會(huì)進(jìn)行類型轉(zhuǎn)換,而 === 不會(huì)。對(duì)于 null 和 undefined,ECMAScript 標(biāo)準(zhǔn)做了一個(gè)特殊的規(guī)定:
null == undefined必須返回true。
這是因?yàn)檎Z(yǔ)言設(shè)計(jì)者認(rèn)為,這兩者都表示 “無(wú)值”,在寬松比較下應(yīng)該被視為相等。但在嚴(yán)格比較下,它們是不同的。
console.log(null == undefined); // true console.log(null === undefined); // false
這就導(dǎo)致了一個(gè)非常有用的簡(jiǎn)寫技巧:如果你想同時(shí)檢查一個(gè)變量是不是 null 或者 undefined,你可以直接寫:
if (value == null) {
// 這會(huì)同時(shí)匹配 null 和 undefined
// 等價(jià)于 if (value === null || value === undefined)
}
這在實(shí)際開發(fā)中非常常用,因?yàn)楹芏鄷r(shí)候我們并不關(guān)心到底是 null 還是 undefined,我們只關(guān)心 “這個(gè)值是不是空的”。
5.2Object.is:終極的相等判斷
ES6 引入了 Object.is() 方法,它解決了 === 無(wú)法處理 NaN 的問(wèn)題。
我們來(lái)看一下三種比較方式的區(qū)別:
| 比較 | == | === | Object.is() |
|---|---|---|---|
null == undefined | true | false | false |
NaN == NaN | false | false | true |
+0 == -0 | true | true | false |
Object.is() 是最嚴(yán)格的相等判斷,它不會(huì)做任何類型轉(zhuǎn)換,也不會(huì)對(duì) NaN 和 -0 做特殊處理。
console.log(Object.is(NaN, NaN)); // true!終于可以正常判斷 NaN 了 console.log(Object.is(null, null)); // true console.log(Object.is(undefined, undefined)); // true
下面的矩陣圖展示了在嚴(yán)格相等(===)下,各個(gè)特殊值之間的比較結(jié)果:

六、實(shí)戰(zhàn)避坑:那些年我們踩過(guò)的雷
6.1 坑 1:濫用if (!value)
很多人喜歡用 if (!value) 來(lái)判斷變量是否為空。但這會(huì)把所有的假值(Falsy Value)都過(guò)濾掉,包括 0、''、false。
// 錯(cuò)誤示范
function processAge(age) {
if (!age) {
console.log('年齡為空');
} else {
console.log('處理年齡', age);
}
}
processAge(0); // 錯(cuò)誤!0 是合法年齡,但被當(dāng)成空了
正確做法:明確檢查 null 和 undefined。
if (age == null) {
console.log('年齡為空');
}
6.2 坑 2:JSON 序列化的丟失
當(dāng)你使用 JSON.stringify 序列化數(shù)據(jù)時(shí),undefined、NaN 和 Infinity 會(huì)被特殊處理:
undefined、函數(shù)、Symbol 會(huì)被忽略(在對(duì)象中)或者變成null(在數(shù)組中)NaN和Infinity會(huì)變成null
JSON.stringify({ a: NaN, b: undefined, c: null });
// 結(jié)果: "{"a":null,"c":null}"
注意,這里 NaN 和 undefined 都變成了 null!這意味著你序列化之后,就再也分不清原來(lái)的是 NaN 還是 undefined 還是 null 了,這在處理后端數(shù)據(jù)時(shí)要格外小心。
6.3 坑 3:默認(rèn)參數(shù)只對(duì) undefined 生效
ES6 的默認(rèn)參數(shù)只有在參數(shù)是 undefined 的時(shí)候才會(huì)觸發(fā),null 不會(huì)!
function greet(name = 'Guest') {
console.log(name);
}
greet(undefined); // Guest (觸發(fā)默認(rèn)值)
greet(null); // null (不觸發(fā)!因?yàn)?null 是一個(gè)明確的傳值)
這也符合語(yǔ)義:undefined 表示 “我沒傳這個(gè)參數(shù)”,而 null 表示 “我傳了,就是空”。
七、最佳實(shí)踐總結(jié)
經(jīng)過(guò)上面的分析,我們可以總結(jié)出一套最佳實(shí)踐,幫助你在日常開發(fā)中正確使用這三個(gè)值:
語(yǔ)義優(yōu)先:
- 讓
undefined處理 “缺失” 的情況,不要手動(dòng)賦值它。 - 用
null表示 “主動(dòng)清空”,當(dāng)你想表示一個(gè)對(duì)象變量為空時(shí)使用它。
- 讓
判斷準(zhǔn)則:
- 檢測(cè)
null:使用value === null - 檢測(cè)
undefined:使用value === undefined或者typeof value === 'undefined'(處理未聲明變量) - 檢測(cè)
NaN:使用Number.isNaN(value),永遠(yuǎn)不要用全局的isNaN() - 同時(shí)檢測(cè)兩者:使用
value == null來(lái)同時(shí)匹配null和undefined,這是一個(gè)安全的簡(jiǎn)寫。
- 檢測(cè)
利用現(xiàn)代語(yǔ)法:
使用空值合并運(yùn)算符
??來(lái)處理默認(rèn)值,它只會(huì)在null/undefined時(shí)生效,不會(huì)誤傷0或''。const count = response.count ?? 0;
使用可選鏈運(yùn)算符
?.來(lái)安全訪問(wèn)屬性,避免Cannot read property of undefined錯(cuò)誤。
結(jié)語(yǔ)
undefined、null 和 NaN,這三個(gè)看似簡(jiǎn)單的值,背后卻隱藏著 JavaScript 類型系統(tǒng)的設(shè)計(jì)哲學(xué)和歷史包袱。
undefined是系統(tǒng)告訴你 “這里沒東西”。null是你告訴系統(tǒng) “這里我故意清空了”。NaN是系統(tǒng)告訴你 “數(shù)字運(yùn)算炸了”。
理解了它們的區(qū)別,你就能在日常開發(fā)中避開絕大多數(shù)與空值相關(guān)的 bug,寫出更清晰、更健壯的前端代碼。
參考資料
- MDN Web Docs. NaN
- MDN Web Docs. undefined
- MDN Web Docs. Object.is()
- MDN Web Docs. 相等比較和相同
- OpenReplay. The Strange Life of NaN in JavaScript
到此這篇關(guān)于前端JavaScript之NaN、undefined、null的文章就介紹到這了,更多相關(guān)JS NaN、undefined、null內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- JavaScript Undefined,Null類型和NaN值區(qū)別
- JavaScript null和undefined區(qū)別分析
- JS基礎(chǔ)之undefined與null的區(qū)別分析
- Javascript 中 null、NaN和undefined的區(qū)別總結(jié)
- js判斷undefined類型,undefined,null, 的區(qū)別詳細(xì)解析
- JS中判斷null、undefined與NaN的方法
- JS中判斷null、undefined與NaN的方法
- 詳解JavaScript中undefined與null的區(qū)別
- js刪除對(duì)象/數(shù)組中null、undefined、空對(duì)象及空數(shù)組方法示例
相關(guān)文章
javascript回調(diào)函數(shù)的概念理解與用法分析
這篇文章主要介紹了javascript回調(diào)函數(shù)的概念理解與用法,結(jié)合具體實(shí)例形式分析了javascript回調(diào)函數(shù)的功能、原理、使用方法與相關(guān)注意事項(xiàng),需要的朋友可以參考下2017-05-05
Rxjs?TakeUntil?操作符內(nèi)容梳理總結(jié)
這篇文章主要介紹了Rxjs?TakeUntil操作符內(nèi)容梳理總結(jié),文章圍繞主題展開詳細(xì)的內(nèi)容介紹,具有一定的參考價(jià)值,需要的小伙伴可以參考一下2022-06-06
JS實(shí)現(xiàn)在網(wǎng)頁(yè)中彈出一個(gè)輸入框的方法
這篇文章主要介紹了JS實(shí)現(xiàn)在網(wǎng)頁(yè)中彈出一個(gè)輸入框的方法,實(shí)例分析了prompt的用法,可用來(lái)設(shè)置密碼,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-03-03
多種方法實(shí)現(xiàn)load加載完成后把圖片一次性顯示出來(lái)
如何一個(gè)load 加載完成后把圖片一次性顯示出來(lái),下面有個(gè)不錯(cuò)的方法,希望對(duì)大家有所幫助2014-02-02
JS如何判斷移動(dòng)端訪問(wèn)設(shè)備并解析對(duì)應(yīng)CSS
本文為大家詳細(xì)介紹下JS如何判斷移動(dòng)端訪問(wèn)設(shè)備并解析對(duì)應(yīng)CSS,感興趣的朋友可以參考下2013-11-11

