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

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

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

