在 JavaScript 中typeof null
的結果為 "object"
,這是從 JavaScript 的第一版遺留至今的一個 bug。並且,因為影響太廣已經永遠無法修復這個 bug 了。我們來挖一挖墳,看看這個 bug 是怎麼來的。
在第一版的 JavaScript 中,變數的值被設計儲存在一個 32 位的記憶體單元中。該單元包含一個 1 或 3 位的型別標誌,和實際資料的值。型別標誌儲存在單元的最後。包括以下五種情況:
- 000:object,資料為物件的引用
- 1:int,資料為 31 位的有符號整型
- 010:double,資料為一個雙精度浮點數的引用
- 100:string,資料為一個字串的引用
- 110:boolean,資料為布林型別的值
除此之外,還有兩種特殊情況:
- undefined 負的 2 的 30 次方(超出當時整型取值範圍的一個數)
- null 空指標
顯而易見,null 的儲存單元最後三位和 object 一樣是 000
。然後讓我們再看一下 typeof
的 實現 :
JS_PUBLIC_API(JSType)
JS_TypeOfValue(JSContext *cx, jsval v)
{
JSType type = JSTYPE_VOID;
JSObject *obj;
JSObjectOps *ops;
JSClass *clasp;
CHECK_REQUEST(cx);
if (JSVAL_IS_VOID(v)) { // (1)
type = JSTYPE_VOID;
} else if (JSVAL_IS_OBJECT(v)) { // (2)
obj = JSVAL_TO_OBJECT(v);
if (obj &&
(ops = obj->map->ops,
ops == &js_ObjectOps
? (clasp = OBJ_GET_CLASS(cx, obj),
clasp->call || clasp == &js_FunctionClass) // (3,4)
: ops->call != 0)) { // (3)
type = JSTYPE_FUNCTION;
} else {
type = JSTYPE_OBJECT;
}
} else if (JSVAL_IS_NUMBER(v)) {
type = JSTYPE_NUMBER;
} else if (JSVAL_IS_STRING(v)) {
type = JSTYPE_STRING;
} else if (JSVAL_IS_BOOLEAN(v)) {
type = JSTYPE_BOOLEAN;
}
return type;
}
程式碼的大致執行步驟:
- 在步驟(1)判斷值
v
是否為undefined(VOID)
,這個檢查通過下面的比較來判斷:#define JSVAL_IS_VOID(v) ((v) == JSVAL_VOID)
- 接下來,在步驟(2)判斷值是否包含
object
標誌,並且通過(3)判斷值是否可以呼叫,或者(4)判斷值是否在屬性[[Class]]
中被標記為函式,來判斷值為函式,否則為物件。 - 接下來的步驟,依次判斷是否為
number
、string
和boolean
。
在步驟(2)中 null
會被判斷為 object
,其實避免這個 bug 的方式很簡單,在步驟(2)之前顯示的檢查值是否為 null
:
#define JSVAL_IS_NULL(v) ((v) == JSVAL_NULL)
考慮 JavaScript 之父在非常緊迫的時間裡完成了這門語言的設計與開發,我們就原諒他吧 :pensive: