在JavaScript中,想要判斷某個對象值屬於哪種內建類型,最靠譜的做法就是通過Object.prototype.toString方法.
var arr = [];console.log(Object.prototype.toString.call(arr)) //"[object Array]"
本文要講的就是,toString方法是如何做到這一點的,原理是什麼.
ECMAScript 3
在ES3中,Object.prototype.toString方法的規範如下:
15.2.4.2 Object.prototype.toString()
在toString方法被調用時,會執行下面的操作步驟:
1. 擷取this對象的[[Class]]屬性的值.
2. 計算出三個字串"[object ", 第一步的操作結果Result(1), 以及 "]"串連後的新字串.
3. 返回第二步的操作結果Result(2).
[[Class]]是一個內部屬性,所有的對象(原生對象和宿主對象)都擁有該屬性.在規範中,[[Class]]是這麼定義的
| 內部屬性 |
描述 |
| [[Class]] |
一個字串值,表明了該對象的類型. |
然後給了一段解釋:
所有內建對象的[[Class]]屬性的值是由本規範定義的.所有宿主對象的[[Class]]屬性的值可以是任意值,甚至可以是內建對象使用過的[[Class]]屬性的值.[[Class]]屬性的值可以用來判斷一個原生對象屬於哪種內建類型.需要注意的是,除了通過Object.prototype.toString方法之外,本規範沒有提供任何其他方式來讓程式訪問該屬性的值(查看 15.2.4.2).
也就是說,把Object.prototype.toString方法返回的字串,去掉前面固定的"[object "和後面固定的"]",就是內部屬性[[class]]的值,也就達到了判斷物件類型的目的.jQuery中的工具方法$.type(),就是幹這個的.
在ES3中,規範文檔並沒有總結出[[class]]內部屬性一共有幾種,不過我們可以自己統計一下,原生對象的[[class]]內部屬性的值一共有10種.分別是:"Array", "Boolean", "Date", "Error", "Function", "Math", "Number", "Object", "RegExp", "String".
ECMAScript 5
在ES5.1中,除了規範寫的更詳細一些以外,Object.prototype.toString方法和[[class]]內部屬性的定義上也有一些變化,Object.prototype.toString方法的規範如下:
15.2.4.2 Object.prototype.toString ( )
在toString方法被調用時,會執行下面的操作步驟:
如果this的值為undefined,則返回"[object Undefined]".
如果this的值為null,則返回"[object Null]".
讓O成為調用ToObject(this)的結果.
讓class成為O的內部屬性[[Class]]的值.
返回三個字串"[object ", class, 以及 "]"串連後的新字串.
可以看出,比ES3多了1,2,3步.第1,2步屬於新規則,比較特殊,因為"Undefined"和"Null"並不屬於[[class]]屬性的值,需要注意的是,這裡和strict 模式無關(大部分函數在strict 模式下,this的值才會保持undefined或null,非strict 模式下會自動成為全域對象).第3步並不算是新規則,因為在ES3的引擎中,也都會在這一步將三種原始實值型別轉換成對應的封裝對象,只是規範中沒寫出來.ES5中,[[Class]]屬性的解釋更加詳細:
所有內建對象的[[Class]]屬性的值是由本規範定義的.所有宿主對象的[[Class]]屬性的值可以是除了"Arguments", "Array", "Boolean", "Date", "Error", "Function", "JSON", "Math", "Number", "Object", "RegExp", "String"之外的的任何字串.[[Class]]內部屬性是引擎內部用來判斷一個對象屬於哪種類型的值的.需要注意的是,除了通過Object.prototype.toString方法之外,本規範沒有提供任何其他方式來讓程式訪問該屬性的值(查看 15.2.4.2).
和ES3對比一下,第一個差別就是[[class]]內部屬性的值多了兩種,成了12種,一種是arguments對象的[[class]]成了"Arguments",而不是以前的"Object",還有就是多個了全域對象JSON,它的[[class]]值為"JSON".第二個差別就是,宿主對象的[[class]]內部屬性的值,不能和這12種值衝突,不過在支援ES3的瀏覽器中,貌似也沒有發現哪些宿主對象故意使用那10個值.
ECMAScript 6
ES6目前還只是工作草案,但能夠肯定的是,[[class]]內部屬性沒有了,取而代之的是另外一個內部屬性[[NativeBrand]].[[NativeBrand]]屬性是這麼定義的:
| 內部屬性 |
屬性值 |
描述 |
| [[NativeBrand]] |
枚舉NativeBrand的一個成員. |
該屬性的值對應一個標誌值(tag value),可以用來區分原生對象的類型. |
[[NativeBrand]]屬性的解釋:
[[NativeBrand]]內部屬性用來識別某個原生對象是否為符合本規範的某一種特定類型的對象.[[NativeBrand]]內部屬性的值為下面這些枚舉類型的值中的一個:NativeFunction, NativeArray, StringWrapper, BooleanWrapper, NumberWrapper, NativeMath, NativeDate, NativeRegExp, NativeError, NativeJSON, NativeArguments, NativePrivateName.[[NativeBrand]]內部屬性僅用來區分區分特定類型的ECMAScript原生對象.只有在表10中明確指出的物件類型才有[[NativeBrand]]內部屬性.
表10 — [[NativeBrand]]內部屬性的值
| 屬性值 |
對應類型 |
| NativeFunction |
Function objects |
| NativeArray |
Array objects |
| StringWrapper |
String objects |
| BooleanWrapper |
Boolean objects |
| NumberWrapper |
Number objects |
| NativeMath |
The Math object |
| NativeDate |
Date objects |
| NativeRegExp |
RegExp objects |
| NativeError |
Error objects |
| NativeJSON |
The JSON object |
| NativeArguments |
Arguments objects |
| NativePrivateName |
Private Name objects |
可見,和[[class]]不同的是,並不是每個對象都擁有[[NativeBrand]].同時,Object.prototype.toString方法的規範也改成了下面這樣:
15.2.4.2 Object.prototype.toString ( )
在toString方法被調用時,會執行下面的操作步驟:
如果this的值為undefined,則返回"[object Undefined]".
如果this的值為null,則返回"[object Null]".
讓O成為調用ToObject(this)的結果.
如果O有[[NativeBrand]]內部屬性,讓tag成為表29中對應的值.
否則
讓hasTag成為調用O的[[HasProperty]]內部方法後的結果,參數為@@toStringTag.
如果hasTag為false,則讓tag為"Object".
否則,
讓tag成為調用O的[[Get]]內部方法後的結果,參數為@@toStringTag.
如果tag是一個abrupt completion,則讓tag成為NormalCompletion("???").
讓tag成為tag.[[value]].
如果Type(tag)不是字串,則讓tag成為"???".
如果tag的值為"Arguments", "Array", "Boolean", "Date", "Error", "Function", "JSON", "Math", "Number", "Object", "RegExp",或
者"String"中的任一個,則讓tag成為字串"~"和tag當前的值串連後的結果.
返回三個字串"[object ", tag, and "]"串連後的新字串.
表29 — [[NativeBrand]] 標誌值
| [[NativeBrand]]值 |
標誌值 |
| NativeFunction |
"Function" |
| NativeArray |
"Array" |
| StringWrapper |
"String" |
| BooleanWrapper |
"Boolean" |
| NumberWrapper |
"Number" |
| NativeMath |
"Math" |
| NativeDate |
"Date" |
| NativeRegExp |
"RegExp" |
| NativeError |
"Error" |
| NativeJSON |
"JSON" |
| NativeArguments |
"Arguments" |
可以看到,在規範上有了很大的變化,不過對於普通使用者來說,貌似感覺不到.
也許你發現了,ES6裡的新類型Map,Set等,都沒有在表29中.它們在執行toString方法的時候返回的是什麼?
console.log(Object.prototype.toString.call(Map())) //"[object Map]"console.log(Object.prototype.toString.call(Set())) //"[object Set]"
其中的字串"Map"是怎麼來的呢:
15.14.5.13 Map.prototype.@@toStringTag
@@toStringTag 屬性的初始值為字串"Map".
由於ES6的規範還在制定中,各種相關規定都有可能改變,所以如果想瞭解更多細節.看看下面這兩個連結,現在只需要知道的是:[[class]]沒了,使用了更複雜的機制.
以上所述是小編給大家分享的JavaScript中Object.prototype.toString方法的原理,希望對大家有所協助!