談談 Object.prototype.toString 。

weixin_34370347發表於2017-05-13

原文連結我的blog

前幾日看到一個比較熟悉的面試題,判斷一個變數是不是陣列?
以下幾種方法供參考:

var arr = [1, 2, 3]
Array.isArray(arr)
arr instanceof Array
arr.constructor === Array
Object.prototype.toString.call(arr) === '[object Array]'
...複製程式碼

這篇文章主要是談談 Object.prototype.toString

ECMAScript 5

在ECMAScript 5中,Object.prototype.toString()被呼叫時,會進行如下步驟:

  • 如果 thisundefined ,返回 [object Undefined]
  • 如果 thisnull , 返回 [object Null]
  • O 為以 this 作為引數呼叫 ToObject 的結果;
  • classO 的內部屬性 [[Class]] 的值;
  • 返回三個字串 "[object", class, 以及"]" 拼接而成的字串。

[[Class]]

[[Class]]是一個內部屬性,值為一個型別字串,可以用來判斷值的型別。

有這麼一段詳細的解釋:

本規範的每種內建物件都定義了 [[Class]] 內部屬性的值。宿主物件的 [[Class]] 內部屬性的值可以是除了 "Arguments", "Array", "Boolean", "Date", "Error", "Function", "JSON", "Math", "Number", "Object", "RegExp", "String" 的任何字串。[[Class]] 內部屬性的值用於內部區分物件的種類。注,本規範中除了通過 Object.prototype.toString ( 見 15.2.4.2) 沒有提供任何手段使程式訪問此值。

在JavaScript程式碼裡,唯一可以訪問該屬性的方法就是通過 Object.prototype.toString ,通常方法如下:

Object.prototype.toString.call(value)複製程式碼

舉例:

> Object.prototype.toString.call(null)
'[object Null]'

> Object.prototype.toString.call(undefined)
'[object Undefined]'

> Object.prototype.toString.call(Math)
'[object Math]'

> Object.prototype.toString.call({})
'[object Object]'

> Object.prototype.toString.call([])
'[object Array]'複製程式碼

因此,可以用下列函式,來獲取任意變數的[[Class]]屬性:

function getClass (a) {
  const str = Object.prototype.toString.call(a)
  return /^\[object (.*)\]$/.exec(str)[1]
}複製程式碼

執行即可得

> getClass(null)
'Null'

> getClass(undefined)
'Undefined'

> getClass(Math)
'Math'

> getClass({})
'Object'

> getClass([])
'Array'複製程式碼

ECMAScript 6

在ES6,呼叫 Object.prototype.toString 時,會進行如下步驟:

  • 如果 thisundefined ,返回 '[object Undefined]' ;
  • 如果 thisnull , 返回 '[object Null]'
  • O 為以 this 作為引數呼叫 ToObject 的結果;
  • isArrayIsArray(O)
  • ReturnIfAbrupt(isArray) (如果 isArray 不是一個正常值,比如丟擲一個錯誤,中斷執行);
  • 如果 isArraytrue , 令 builtinTag'Array' ;
  • else ,如果 O is an exotic String object , 令 builtinTag'String'
  • else ,如果 O 含有 [[ParameterMap]] internal slot, , 令 builtinTag'Arguments'
  • else ,如果 O 含有 [[Call]] internal method , 令 builtinTagFunction
  • else ,如果 O 含有 [[ErrorData]] internal slot , 令 builtinTagError
  • else ,如果 O 含有 [[BooleanData]] internal slot , 令 builtinTagBoolean
  • else ,如果 O 含有 [[NumberData]] internal slot , 令 builtinTagNumber
  • else ,如果 O 含有 [[DateValue]] internal slot , 令 builtinTagDate
  • else ,如果 O 含有 [[RegExpMatcher]] internal slot , 令 builtinTagRegExp
  • else , 令 builtinTagObject
  • tagGet(O, @@toStringTag) 的返回值( Get(O, @@toStringTag) 方法,既是在 O 是一個物件,並且具有 @@toStringTag 屬性時,返回 O[Symbol.toStringTag] );
  • ReturnIfAbrupt(tag) ,如果 tag 是正常值,繼續執行下一步;
  • 如果 Type(tag) 不是一個字串,let tag be builtinTag
  • 返回由三個字串 "[object", tag, and "]" 拼接而成的一個字串。

在ES6裡,之前的 [[Class]] 不再使用,取而代之的是一系列的 internal slot ,有一個比較完整的解釋:

Internal slots correspond to internal state that is associated with objects and used by various ECMAScript specification algorithms. Internal slots are not object properties and they are not inherited. Depending upon the specific internal slot specification, such state may consist of values of any ECMAScript language type or of specific ECMAScript specification type values

大概的意思是:Internal slots 對應於與物件相關聯並由各種ECMAScript規範演算法使用的內部狀態,它們沒有物件屬性,也不能被繼承,根據具體的 Internal slot 規範,這種狀態可以由任何ECMAScript語言型別或特定ECMAScript規範型別值的值組成。

此外,通過對 Object.prototype.toString 在ES6的實現步驟分析,我們其實可以很容易改變 Object.prototype.toString.call 的結果,像下面一樣:

let obj = {}

Object.defineProperty(obj, Symbol.toStringTag, {
    get: function() {
        return "newClass"
    }
})

console.log(Object.prototype.toString.call(obj)) // "[object newClass]"複製程式碼

ECMAScript 7

ES7目前還是工作草案,到目前為止,就 Object.prototype.toString 的實現步驟來說, 只是移除了ES6其中的 ReturnIfAbrupt

參考

  1. www.ecma-international.org/ecma-262/5.…
  2. www.adobe.com/devnet/arch…
  3. developer.mozilla.org/en-US/docs/…
  4. www.ecma-international.org/ecma-262/6.…
  5. es6.ruanyifeng.com/#docs/symbo…
  6. tc39.github.io/ecma262/#se…

完。