這篇文章算是對《JavaScript 函數的特性與原型鏈講解》 的一個補充,若是你尚未看過那篇文章,能夠先大體瀏覽一下。html
寫這篇文章的緣由是分享完《JavaScript 函數的特性與原型鏈講解》後,被問到爲何 typeof 一個函數會返回 "function"。此次就給你們講解一下 typeof 是如何看待函數的,但願能給你們帶來幫助,同時也做爲上篇文章的補充。c#
咱們先來回顧一下最新的 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
這個包裹對象的 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 可能的返回值: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 函數和對象的時候,是根據這種方式來分析的:
之前面的 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" 纔對啊。
帶着這個問題,咱們來看一點 JavaScript 的歷史。
你們都知道 "typeof null" 的 bug,它是在 JavaScript 的初版就存在的。在這個版本中,值以 32 位的單位存儲,包括一個小型類型標記(1-3 位)和值的實際數據。類型標記存儲在單元的較低位上。一共有 5 種類型:
兩個值比較特殊:
undefined (JSVAL_VOID) 的值是 −2^30 整型(一個超出整型範圍的數)。
null (JSVAL_NULL) 是機器碼空指針。或者是:一個對象類型標記加上一個爲零的引用。
如今應該很清楚爲何 typeof 認爲 null 是一個對象了:它檢查了類型標記和類型標記表示的對象。
回顧完初版的歷史,咱們再來看一下 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 的 typeof 是如何對待函數和對象類型的:
不愧是標準,簡單直接。
執行與此對象關聯的代碼。經過函數調用表達式調用。內部方法的參數是一個 this 值和一個包含調用表達式傳遞給函數的參數的列表。實現此內部方法的對象是可調用的。
這是翻譯的原話,簡單點說,一個對象若是支持了內部的 [[Call]] 方法,那麼它就能夠被調用,就變成了函數,因此叫作函數對象。
相應地,若是一個函數對象支持了內部的 [[Construct]] 方法,那麼它就能夠使用 new 或 super 來調用,這時咱們就能夠把這個函數對象稱爲:構造函數。
typeof 原理就大體分析到這裏了,我對函數對象和構造函數又有了更清晰的認識,跟各位一塊兒學習進步,若是文章中有不對的地方還請指正。
參考資料: