所有的悲傷,總會留下一絲歡樂的線索,所有的遺憾,總會留下一處完美的角落,我在冰峰的深海,尋找希望的缺口,卻在驚醒時,瞥見絕美的陽光! ——幾米 本文為讀 lodash 源碼的第十八篇,後續文章會更新到這個倉庫中,歡迎 star: "pocket lodash" gitbook也會同步倉庫的更新,gi ...
所有的悲傷,總會留下一絲歡樂的線索,所有的遺憾,總會留下一處完美的角落,我在冰峰的深海,尋找希望的缺口,卻在驚醒時,瞥見絕美的陽光!
——幾米
本文為讀 lodash 源碼的第十八篇,後續文章會更新到這個倉庫中,歡迎 star:pocket-lodash
gitbook也會同步倉庫的更新,gitbook地址:pocket-lodash
作用與用法
我們都知道,可以借用 Object
原型上的 toString
方法來獲取數據的類型。 baseGetTag
利用的也是這一特性,其返回的結果如 [object String]
這樣的形式,調用方式如下:
baseGetTag('string') // [object String]
為什麼可以用Object.prototype.toString
先看 es5
規範對 Object.prototyep.toString
的運行步驟規定:
當調用 toString 方法,採用如下步驟:
- 如果 this 的值是 undefined, 返回 "[object Undefined]".
- 如果 this 的值是 null, 返回 "[object Null]".
- 令 O 為以 this 作為參數調用 ToObject 的結果 .
- 令 class 為 O 的 [[Class]] 內部屬性的值 .
- 返回三個字元串 "[object ", class, and "]" 連起來的字元串 .
在第三步的時候,會調用 ToObject
來轉換成對象,而轉換成對象後,會有個 [[Class]]
的內部屬性,而這個內部屬性的值正是 toString
的關鍵部分。
接下來再看規範對 [[Class]]
的規定:
本規範的每種內置對象都定義了 [[Class]] 內部屬性的值。宿主對象的 [[Class]] 內部屬性的值可以是除了 "Arguments", "Array", "Boolean", "Date", "Error", "Function", "JSON", "Math", "Number", "Object", "RegExp", "String" 的任何字元串。[[Class]] 內部屬性的值用於內部區分對象的種類。註,本規範中除了通過 Object.prototype.toString ( 見 15.2.4.2) 沒有提供任何手段使程式訪問此值。
由規範可見,要獲取這個 [[Class]]
內部屬性的值的唯一手段是通過 Object.prototype.toString
。
源碼分析
源碼如下:
const objectProto = Object.prototype
const hasOwnProperty = objectProto.hasOwnProperty
const toString = objectProto.toString
const symToStringTag = typeof Symbol != 'undefined' ? Symbol.toStringTag : undefined
function baseGetTag(value) {
if (value == null) {
return value === undefined ? '[object Undefined]' : '[object Null]'
}
if (!(symToStringTag && symToStringTag in Object(value))) {
return toString.call(value)
}
const isOwn = hasOwnProperty.call(value, symToStringTag)
const tag = value[symToStringTag]
let unmasked = false
try {
value[symToStringTag] = undefined
unmasked = true
} catch (e) {}
const result = toString.call(value)
if (unmasked) {
if (isOwn) {
value[symToStringTag] = tag
} else {
delete value[symToStringTag]
}
}
return result
}
export default baseGetTag
Symbol.toStringTag
在 ES6
中,規範對 Object.prototype.toString
的步驟進行了重新定義,不再使用 [[Class]]
的內部屬性進行獲取,具體的規範如下:
在ES6,調用
Object.prototype.toString
時,會進行如下步驟:
- 如果
this
是undefined
,返回'[object Undefined]'
;- 如果
this
是null
, 返回'[object Null]'
;- 令
O
為以this
作為參數調用ToObject
的結果;- 令
isArray
為IsArray(O)
;ReturnIfAbrupt(isArray)
(如果isArray
不是一個正常值,比如拋出一個錯誤,中斷執行);- 如果
isArray
為true
, 令builtinTag
為'Array'
;else
,如果O is an exotic String object
, 令builtinTag
為'String'
;else
,如果O
含有[[ParameterMap]] internal slot,
, 令builtinTag
為'Arguments'
;else
,如果O
含有[[Call]] internal method
, 令builtinTag
為Function
;else
,如果O
含有[[ErrorData]] internal slot
, 令builtinTag
為Error
;else
,如果O
含有[[BooleanData]] internal slot
, 令builtinTag
為Boolean
;else
,如果O
含有[[NumberData]] internal slot
, 令builtinTag
為Number
;else
,如果O
含有[[DateValue]] internal slot
, 令builtinTag
為Date
;else
,如果O
含有[[RegExpMatcher]] internal slot
, 令builtinTag
為RegExp
;else
, 令builtinTag
為Object
;- 令
tag
為Get(O, @@toStringTag)
的返回值(Get(O, @@toStringTag)
方法,既是在O
是一個對象,並且具有@@toStringTag
屬性時,返回O[Symbol.toStringTag]
);ReturnIfAbrupt(tag)
,如果tag
是正常值,繼續執行下一步;- 如果
Type(tag)
不是一個字元串,let tag be builtinTag
;- 返回由三個字元串
"[object", tag, and "]"
拼接而成的一個字元串。
規範對類型的判斷進行了細化,前15步可以看成跟 es5
的作用一樣,獲取到數據的類型 builtinTag
,但是第16步調用了 @@toStringTag
的方法,如果再看規範的描述,可以知道這個其實是對象中的 Symbol.toStringTag
屬性,如果這個屬性返回的是一個字元串,則採用這個返回值 tag
作為數據的類型,否則才採用 builtinTag
。
處理null和undefined
if (value == null) {
return value === undefined ? '[object Undefined]' : '[object Null]'
}
這裡是處理瀏覽器相容性,在 es5
之前,並沒有對 null
和 undefined
進行處理,所以返回的都是 [object Object]
。
處理不含Symbol.toStringTag的情況
if (!(symToStringTag && symToStringTag in Object(value))) {
return toString.call(value)
}
如果瀏覽器不支持 Symbol
或者 value
並不存在 Symbol.toStringTag
的方法,則可以直接調用 toString
,將結果返回了。
處理Symbol.toStringTag 的情況
const isOwn = hasOwnProperty.call(value, symToStringTag)
const tag = value[symToStringTag]
let unmasked = false
try {
value[symToStringTag] = undefined
unmasked = true
} catch (e) {}
const result = toString.call(value)
if (unmasked) {
if (isOwn) {
value[symToStringTag] = tag
} else {
delete value[symToStringTag]
}
}
為了避免 Symbol.toStringTag
的影響,先將 value
的 Symbol.toStringTag
設置為 undefined
,這樣可以屏蔽掉原型鏈上的 Symbol.toStringTag
屬性,然後再使用 toString
方法獲取到 value
的屬性描述。
在獲取到屬性描述後,如果 Symbol.toStringTag
為自身的屬性(不為原型鏈上的屬性),則將原來保存下來的 tag
重新賦值,否則將 Symbol.toStringTag
屬性移除。
參考
談談 Object.prototype.toString 。
License
署名-非商業性使用-禁止演繹 4.0 國際 (CC BY-NC-ND 4.0)
最後,所有文章都會同步發送到微信公眾號上,歡迎關註,歡迎提意見:
作者:對角另一面