【譯】"Typeof null" 的歷史

Tao-Quixote發表於2017-12-28

Update 2013-11-05:為了更好的解釋為什麼 'typeof null' 的結果是 'object',我查閱了實現的 C 原始碼。

在 JavaScript 中,typeof null 的結果是 object,該結果錯誤地暗示了 null 是一個物件(null 並不是一個物件,而是一個基本資料型別(的值),詳情可以查閱我的博文 categorizing values)。這其實是一個 bug,但更糟的是這個 bug 是不能修復的,因為修復這個 bug 會使已經存在的程式崩潰。下面就讓我們探索一下這個 bug 的歷史。

typeof null 這個 bug 是 JavaScript 第一個版本遺留下來的。在這個版本中,所有值都儲存在 32 位的單元中,每個單元包含一個小的**型別標籤(1-3 bits)**以及當前要儲存值的真實資料。型別標籤儲存在每個單元的低位中,共有五種資料型別:

  • 000: object - 當前儲存的資料指向一個物件。
  • 1: int - 當前儲存的資料是一個 31 位的有符號整數。
  • 010: double - 當前儲存的資料指向一個雙精度的浮點數。
  • 100: string - 當前儲存的資料指向一個字串。
  • 110: boolean - 當前儲存的資料是布林值。

如果最低位是 1,則型別標籤標誌位的長度只有一位;如果最低位是 0,則型別標籤標誌位的長度佔三位,為儲存其他四種資料型別提供了額外兩個 bit 的長度。

有兩種特殊資料型別:

  • undefined(JSVAL_VOID) 的值是 -2**30(-2 的 30 次方) (一個超出整數範圍的數字)
  • null(JSVAL_NULL) 的值是機器碼 NULL 指標(null 指標的值全是 0)。或者:object 型別的型別標籤 + 0 的引用。

現在可以很明顯地知道為什麼 typeof 操作符會認為 null 是物件了:typeof 操作符檢測 null 的型別標籤位時發現是 000 (存放機器碼 NULL 指標的儲存單元中的所有資料位都是 0,所以低三位也是 0)。下面是 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) 檢查是否為 undefined
            type = JSTYPE_VOID;
        } else if (JSVAL_IS_OBJECT(v)) {  // (2) 檢查是否為 object(低三位是 000)
            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 型別的型別標籤。如果該值具有 object 型別的型別標籤,並可以呼叫(3)或者其內部屬性 [[Class]] 標記它為函式(4),則該值是一個函式;否則,該值就是一個物件。這就是 typeof null 表示式生成的結果。
  • 隨後檢查分別為是否為 number,string 以及 boolean。甚至沒有專門的步驟來檢查是否為 null,該檢查可通過如下的 C 巨集定義來實現:
#define JSVAL_IS_NULL(v)  ((v) == JSVAL_NULL)
複製程式碼

這或許看起來是一個非常明顯的 bug,但是不要忘了實現 JavaScript 第一個版本的時間非常緊迫。

鳴謝:感謝 Tom Schuster(@evilpies) 指引我去看傳統 JavaScript 的原始碼

Source Link ?

本文翻譯自 Dr. Axel Rauschmayer 的博文,侵刪。

轉載請聯絡作者並註明出處。

Translator Info ?

相關文章