寫在前面
JavaScript中有兩個特殊的值,undefined與null。平常在寫項目時,遇到需要判斷一個值是否為空的時候,我總會想到undefined與null。
既然都是代表空值(以前我就是這麼認為的),那麼它們有沒有差別呢?反正我是一直傻傻分不清楚,看了又忘了。最近查閱了一些資料,才發現自己以前真的是誤解它們了,知錯就改,下面總結一下它們的用法和差別。
基本資料類型
在介紹undefined與null之前,我們先來了解一下ECMAScript中的資料類型。在ECMAScript中有五種簡單資料類型(也稱為基本資料類型): Undefined、Null、Boolean、Number 和 String 。還有一種複雜資料類型——Object。
沒錯,首字母大寫的Undefined與Null其實都屬于ECMAScript中的基本資料類型。這兩個資料類型是五種資料類型中最特殊的兩個類型,因為它們都隻有唯一的一個值,分别是undefined與null,就是我們今天要介紹的兩個主角。
Undefined 類型
定義
上面我們說過了,Undefined類型隻有一個值,就是特殊的undefined,在兩種情況下我們會得到undefined:
1、聲明了一個變量,但未對其初始化時,這個變量的值就是undefined。
var
console.log(data === undefined); //true
那麼我麼是否可以顯式地把一個變量初始化為undefined呢,答案是可以的。
var data = undefined;
console.log(data === undefined); //true
var value = 1;
console.log(data); //1
value = undefined;
console.log(data === undefined); // true
一般而言,我們不存在需要顯式地把一個變量設定為undefined值的情況,因為對于未經初始化的值預設就會取得undefined值,而已經初始化的值再将其指派為undefined來表示空值是沒有意義且不可取的。況且字面值undefined的主要目的以用于比較,來區分空對象指針(後面我們會介紹到這指的就是null)與未經初始化的變量的情況。
2、對未定義的變量執行typeof操作符也會傳回undefined
//data變量未定義
var
console.log(typeof data); // "undefined"
console.log(typeof value); // "undefined"
這裡我們沒有使用
===
來判斷,因為對于尚未聲明過的變量,我們隻能執行一項操作,即使用typeof操作符檢測其資料類型,使用其他的操作都會報錯。
//data變量未定義
console.log(data === undefined); //報錯
結果表明對未初始化和未聲明的變量執行typeof操作符都傳回了undefined值,這個結果有其邏輯上的合理性。因為雖然這兩種變量從技術角度看有本質差別,但實際上無論對哪種變量也不可能執行真正的操作。
還有其他幾種情況也會傳回undefined,比如一個函數如果沒有使用return語句指定傳回值,就會傳回一個undefined值,或者調用函數時沒有傳參數值,參數同樣也會被初始化為undefined值。這些都是屬于上面兩種情況在代碼中的展現,這裡就不單獨解釋了。
全局屬性 window.undefined
從上面的例子我們可以看出,無論我們是否初始化過變量,都可以給變量指派為undefined。
其實這裡用于指派的undefined不是一個值,它是一個屬性名,undefined是全局對象的一個屬性,也就是說,它是全局作用域的一個變量,即
window.undefined
,而
window.undefined
這個屬性的值才是前面所說的原始值undefined。
data = undefined;
這就相當于把一個變量
window.undefined
的值指派給另一個變量
data
,這個值就是原始值undefined。
其實在JavaScript代碼中,我們看到的undefined大多數情況指的都是
window.undefined
(本篇文章中多數情況下也是,原始值undefined除外),原始值undefined多數情況下隻存在于文檔或規範中,不存在于JavaScript代碼中(具體可以了解為代碼中參與判斷、比較或指派的都是
window.undefined
,而在控制台中輸出,或函數中傳回的則是原始值undefined)。
console.log(window.undefined); //原始值undefined
注意,在ES3之前其實是沒有原始值undefined這個值的,第三版引入這個值,其實是為了正式區分空對象指針(後面我們會介紹到這指的就是null)與未經初始化的變量。
在ES3中,
window.undefined
就是一個普通的屬性,我們完全可以把它的值改為任何真值。但從ES5之後,
window.undefined
成了一個不可寫,不可配置的資料屬性,它的值永遠是undefined。
局部屬性 undefined
大家可能注意到了,上面我提到的是在大多數情況下undefined指的都是
window.undefined
,那還有什麼其他情況嗎?
其實在ECMAScript中,undefined不是一個保留字,這意味着什麼呢?也就是說我們可以将undefined作為一個局部變量來使用,就像局部作用域中任何其他普通變量一樣,沒有任何特殊性,我們可以對其賦予任何類型的值。
(function() {
var undefined = 'not is undefined';
console.log(undefined); //"not is undefined"
console.log(typeof undefined) // "string"
})()
我們可以看到undefined的值和類型都已經改變,這樣的做法是非常不友好的,這樣會使我們的代碼難以維護和排錯。
undefined 判斷
如何判斷一個變量是否為undefined,這裡有兩種方法。
- 使用嚴格相等符
或不相等操作符===
來決定一個變量是否擁有值,這裡不使用标準相等操作符!==
,是因為标準相等符還會會檢查變量是不是為null,但是嚴格相等操作符不會檢查。null不等同于undefined,這點我們會在後面講到。==
- 使用typeof操作符,這種方式我們在上面已經使用過了,對未定義的變量檢測時隻能使用這種方式,要不然會出現報錯。
void 0
上面我們提到過了,undefined作為局部變量使用是可以被重寫的,那麼如果我們使用下面這種判斷方式,是有風險的。
if(data === undefined){
//do something
}
那麼我們怎樣做才能確定萬無一失呢?讓我們先來了解一下void運算符,官方文檔是這樣解釋的:
The void operator evaluates the given expression and then returns undefined.
void 運算符 對給定的表達式進行求值,然後傳回 undefined
什麼意思呢?就是使用void對後面的表達式求值,無論結果是多少,都會傳回原始值undefined。是以我們可以用
void 0
來代替undefined進行判斷,因為
void 0
始終傳回的都是原始值undefined。
var
console.log(data === void 0); //true
Null類型
定義
Null類型是第二個隻有一個值的資料類型,這個特殊的值就是null。值 null 是一個字面量,它不像undefined 是全局對象的一個屬性。
從邏輯角度來看,null值表示一個空對象指針,訓示變量未指向任何對象。把 null 作為尚未建立的對象,也許更好了解。在 APIs 中,null 常在傳回類型是對象,但沒關聯值的地方使用,就像下面一個例子。
//document.getElementById() 可以傳回對擁有指定 ID 的第一個對象的引用
var $container = document.getElementById("container"); // 注意:container是不存在的
console.log($container); // null
typeof null
當我們使用typeof操作符檢測null值,我們理所應當地認為應該返”Null”類型呀,但是事實傳回的類型卻是”object”。
var data = null;
console.log(typeof data); // "object"
是不是很奇怪?其實我們可以從兩方面來了解這個結果
- 一方面從邏輯角度來看,null值表示一個空對象指針,它代表的其實就是一個空對象,是以使用typeof操作符檢測時傳回”object”也是可以了解的。
- 另一方面,其實在JavaScript 最初的實作中,JavaScript 中的值是由一個表示類型的标簽和實際資料值表示的。對象的類型标簽是 0。由于 null 代表的是空指針(大多數平台下值為 0x00),是以,null的類型标簽也成為了 0,typeof null就錯誤的傳回了”object”。在ES6中,當時曾經有提案為曆史平凡, 将type null的值糾正為null, 但最後提案被拒了,是以還是保持”object”類型。
null 判斷
null的判斷可以使用嚴格相等符
===
或不相等操作符
!==
判斷,不使用标準相等符的原因是因為undefined會影響判斷結果。和undefined不一樣,不能使用typeof來判斷一個值是否為null,原因上邊已經講了,使用typeof來檢測null會傳回”object”,這樣的話我們是沒辦法判斷的。
if(data === null){
console.log("data中沒有儲存對象引用!");
}
null 使用
那麼我們在什麼情況下需要将變量指派為null呢?這裡我想到的有兩種情況。
- 如果定義的變量在将來用于儲存對象,那麼最好将該變量初始化為null,而不是其他值。換句話說,隻要意在儲存對象的變量還沒有真正儲存對象,就應該明确地讓該變量儲存null值,這樣有助于進一步區分null和undefined。
- 當一個資料不再需要使用時,我們最好通過将其值設定為null來釋放其引用,這個做法叫做解除引用。不過解除一個值的引用并不意味着自動回收改值所占用的記憶體。解除引用的真正作用是讓值脫離執行環境,以便垃圾收集器在下次運作時将其回收。解除引用還有助于消除有可能出現的循環引用的情況。這一做法适用于大多數全局變量和全局對象的屬性,局部變量會在它們離開執行環境時(函數執行完時)自動被解除引用。
undefined 與 null
實際上undefined值是派生自null值的,是以ECMA-262規定對它們的相等性測試要傳回true:
console.log(null == undefined); //true
因為使用的是标準相等符
==
,這個操作符出于目的會轉換其操作數為相同類型後再做比較,如果我們使用嚴格相等符比較,我們會發現它們是不相等的,因為嚴格相等符不會進行類型轉換,然而undefined與null屬于不同的類型,是以不相等。
console.log(null === undefined); //false
盡管null和undefined有這樣的關系,但上面我們已經提到過了,它們的用途完全不同,我們在平常使用時一定要學會區分。