JavaScript 的 typeof 原理小記

篇首語

這篇文章算是對《JavaScript 函數的特性與原型鏈講解》 的一個補充,若是你尚未看過那篇文章,能夠先大體瀏覽一下。html

寫這篇文章的緣由是分享完《JavaScript 函數的特性與原型鏈講解》後,被問到爲何 typeof 一個函數會返回 "function"。此次就給你們講解一下 typeof 是如何看待函數的,但願能給你們帶來幫助,同時也做爲上篇文章的補充。c#

回顧 JS 的數據類型

咱們先來回顧一下最新的 ECMAScript 標準的 8 種數據類型:api

7 種原始類型(或稱做基本類型):Boolean、Null、Undefined、Number、BigInt、String、Symbol 和 Object。數組

基本類型(基本數值、基本數據類型)是一種既非對象也無方法的數據。在 JavaScript 中,共有7種基本類型:string,number,bigint,boolean,null,undefined,symbol (ECMAScript 2016新增)。函數

多數狀況下,基本類型直接表明了最底層的語言實現。post

全部基本類型的值都是不可改變的。但須要注意的是,基本類型自己和一個賦值爲基本類型的變量的區別。變量會被賦予一個新值,而原值不能像數組、對象以及函數那樣被改變。學習

何謂不可改變

var bar = "baz";
bar[0] = "a";
console.log(bar[0]); // "b"
console.log(bar);// "baz"
複製代碼

基本類型的包裝對象

除了 null 和 undefined 外,全部基本類型都有其對應的包裝對象:ui

  • String 爲字符串基本類型。
  • Number 爲數值基本類型。
  • BigInt 爲大整數基本類型。
  • Boolean 爲布爾基本類型。
  • Symbol 爲字面量基本類型。

這個包裹對象的 valueOf() 方法返回基本類型值。this

var s_prim = "foo";
var s_obj = new String(s_prim);

console.log(typeof s_prim); // "string"
console.log(typeof s_obj);  // "object"
console.log(typeof s_obj.valueOf());  // "string"
複製代碼

typeof 可能的返回值表

下表總結了 typeof 可能的返回值:spa

類型 結果
Undefined "undefined"
Null "object" (見下文)
Boolean "boolean"
Number "number"
BigInt "bigint"
String "string"
Symbol (ECMAScript 2015 新增) "symbol"
宿主對象(由 JS 環境提供) 取決於具體實現
Function 對象 (按照 ECMA-262 規範實現 [[Call]]) "function"
其餘任何對象 "object"

分析 typeof 的誤區

我以前在分析 typeof 函數和對象的時候,是根據這種方式來分析的:

之前面的 String 爲例:

var s_obj = new String(s_prim);
console.log(typeof String); // "function"
// 使用 typeof 調用函數的時候,是把函數當作函數對象來調用的,又由於全部的函數對象都是由 Function 構造出來的,因此:String.__Proto__ === Function.prototype,因此結果返回了 "function"

console.log(typeof s_obj);  // "object"
// 1. 由於 s_obj 是一個對象,它是由 String 構造出來的,因此:S_obj.__proto__ === String.prototype;
// 2. 又由於全部的構造函數都是由 Object 派生出來的,因此:String.prototype.__proto__ === Object.prototype;
// 3. 因此 s_obj 返回 "object";
複製代碼

若是你看過個人這邊文章《JavaScript 函數的特性與原型鏈講解》 那麼應該能理解我爲何會這麼分析,由於原型鏈是這樣的。

可是問題點出在,typeof 是提早把函數的原型鏈終止了嗎?

// 由於
Function.prototype.__proto__ === Object.prototype;
複製代碼

這麼看來,若是不提早終止原型鏈,那麼 typeof 的結果應該返回 "object" 纔對啊。

一小段 JS 歷史

帶着這個問題,咱們來看一點 JavaScript 的歷史。

你們都知道 "typeof null" 的 bug,它是在 JavaScript 的初版就存在的。在這個版本中,值以 32 位的單位存儲,包括一個小型類型標記(1-3 位)和值的實際數據。類型標記存儲在單元的較低位上。一共有 5 種類型:

  • 000: object,表示這個數據是一個對象的引用。
  • 1: int,表示這個數據是一個 31 位的有符號整型。
  • 010: double,表示這個數據是一個雙精度浮點數的引用。
  • 100: string,表示這個數據是一個字符串的引用。
  • 110: boolean,表示這個數據是一個布爾值。

兩個值比較特殊:

  • undefined (JSVAL_VOID) 的值是 −2^30 整型(一個超出整型範圍的數)。

  • null (JSVAL_NULL) 是機器碼空指針。或者是:一個對象類型標記加上一個爲零的引用。

如今應該很清楚爲何 typeof 認爲 null 是一個對象了:它檢查了類型標記和類型標記表示的對象。

mozilla 的 typeof 的源碼

回顧完初版的歷史,咱們再來看一下 1998 年 mozilla 的 typeof 的源碼:

JS_PUBLIC_API(JSType)
JS_TypeOfValue(JSContext *cx, jsval v)
{
    JSType type = JSTYPE_VOID;
    JSObject *obj;
    JSObjectOps *ops;
    JSClass *clasp;

    CHECK_REQUEST(cx);
    // 檢查 v 是不是 undefined 
    if (JSVAL_IS_VOID(v)) {
        type = JSTYPE_VOID;
    } 
    // 檢查 v 是否有 object 的類型標記
    else if (JSVAL_IS_OBJECT(v)) {
        obj = JSVAL_TO_OBJECT(v);
        // 若是 obj 是可調用的,或者是內部的 [[Class]] 屬性標記了它是一個函數,就返回 function
        if (obj &&
            (ops = obj->map->ops,
             ops == &js_ObjectOps
             ? (clasp = OBJ_GET_CLASS(cx, obj),
                clasp->call || clasp == &js_FunctionClass)
             : ops->call != 0)) {
            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;
}
複製代碼

ES6 是如何解釋的

咱們再來看一下 ES6 的 typeof 是如何對待函數和對象類型的:

  1. 若是一個對象(Object)沒有實現 [[Call]] 內部方法,那麼它就返回 object
  2. 若是一個對象(Object)實現了 [[Call]] 內部方法,那麼它就返回 function

不愧是標準,簡單直接。

[[Call]] 是什麼

執行與此對象關聯的代碼。經過函數調用表達式調用。內部方法的參數是一個 this 值和一個包含調用表達式傳遞給函數的參數的列表。實現此內部方法的對象是可調用的。

這是翻譯的原話,簡單點說,一個對象若是支持了內部的 [[Call]] 方法,那麼它就能夠被調用,就變成了函數,因此叫作函數對象。

相應地,若是一個函數對象支持了內部的 [[Construct]] 方法,那麼它就能夠使用 new 或 super 來調用,這時咱們就能夠把這個函數對象稱爲:構造函數。

typeof 原理就大體分析到這裏了,我對函數對象和構造函數又有了更清晰的認識,跟各位一塊兒學習進步,若是文章中有不對的地方還請指正。

參考資料:

相關文章
相關標籤/搜索