所有的悲傷,總會留下一絲歡樂的線索,所有的遺憾,總會留下一處完美的角落,我在冰峰的深海,尋找希望的缺口,卻在驚醒時,瞥見絕美的陽光!
——幾米
本文為讀 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)
最後,所有文章都會同步傳送到微信公眾號上,歡迎關注,歡迎提意見:
作者:對角另一面