lodash源码分析之获取数据类型

全部的悲伤,总会留下一丝欢乐的线索,全部的遗憾,总会留下一处完美的角落,我在冰峰的深海,寻找但愿的缺口,却在惊醒时,瞥见绝美的阳光!javascript

——几米java

本文为读 lodash 源码的第十八篇,后续文章会更新到这个仓库中,欢迎 star:pocket-lodashgit

gitbook也会同步仓库的更新,gitbook地址:pocket-lodashes6

做用与用法

咱们都知道,能够借用 Object 原型上的 toString 方法来获取数据的类型。 baseGetTag 利用的也是这一特性,其返回的结果如 [object String] 这样的形式,调用方式以下:github

baseGetTag('string') // [object String] 
复制代码

为何能够用Object.prototype.toString

先看 es5 规范对 Object.prototyep.toString 的运行步骤规定:浏览器

当调用 toString 方法,采用以下步骤:微信

  1. 若是 this 的值是 undefined, 返回 "[object Undefined]".
  2. 若是 this 的值是 null, 返回 "[object Null]".
  3. 令 O 为以 this 做为参数调用 ToObject 的结果 .
  4. 令 class 为 O 的 [[Class]] 内部属性的值 .
  5. 返回三个字符串 "[object ", class, and "]" 连起来的字符串 .

在第三步的时候,会调用 ToObject 来转换成对象,而转换成对象后,会有个 [[Class]] 的内部属性,而这个内部属性的值正是 toString 的关键部分。源码分析

接下来再看规范对 [[Class]] 的规定:ui

本规范的每种内置对象都定义了 [[Class]] 内部属性的值。宿主对象的 [[Class]] 内部属性的值能够是除了 "Arguments", "Array", "Boolean", "Date", "Error", "Function", "JSON", "Math", "Number", "Object", "RegExp", "String" 的任何字符串。[[Class]] 内部属性的值用于内部区分对象的种类。注,本规范中除了经过 Object.prototype.toString ( 见 15.2.4.2) 没有提供任何手段使程序访问此值。this

由规范可见,要获取这个 [[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 时,会进行以下步骤:

  1. 若是 thisundefined ,返回 '[object Undefined]' ;
  2. 若是 thisnull , 返回 '[object Null]'
  3. O 为以 this 做为参数调用 ToObject 的结果;
  4. isArrayIsArray(O)
  5. ReturnIfAbrupt(isArray) (若是 isArray 不是一个正常值,好比抛出一个错误,中断执行);
  6. 若是 isArraytrue , 令 builtinTag'Array' ;
  7. else ,若是 O is an exotic String object , 令 builtinTag'String'
  8. else ,若是 O 含有 [[ParameterMap]] internal slot, , 令 builtinTag'Arguments'
  9. else ,若是 O 含有 [[Call]] internal method , 令 builtinTagFunction
  10. else ,若是 O 含有 [[ErrorData]] internal slot , 令 builtinTagError
  11. else ,若是 O 含有 [[BooleanData]] internal slot , 令 builtinTagBoolean
  12. else ,若是 O 含有 [[NumberData]] internal slot , 令 builtinTagNumber
  13. else ,若是 O 含有 [[DateValue]] internal slot , 令 builtinTagDate
  14. else ,若是 O 含有 [[RegExpMatcher]] internal slot , 令 builtinTagRegExp
  15. else , 令 builtinTagObject
  16. tagGet(O, @@toStringTag) 的返回值( Get(O, @@toStringTag) 方法,既是在 O 是一个对象,而且具备 @@toStringTag 属性时,返回 O[Symbol.toStringTag] );
  17. ReturnIfAbrupt(tag) ,若是 tag 是正常值,继续执行下一步;
  18. 若是 Type(tag) 不是一个字符串,let tag be builtinTag
  19. 返回由三个字符串 "[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 以前,并无对 nullundefined 进行处理,因此返回的都是 [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 的影响,先将 valueSymbol.toStringTag 设置为 undefined ,这样能够屏蔽掉原型链上的 Symbol.toStringTag 属性,而后再使用 toString 方法获取到 value 的属性描述。

在获取到属性描述后,若是 Symbol.toStringTag 为自身的属性(不为原型链上的属性),则将原来保存下来的 tag 从新赋值,不然将 Symbol.toStringTag 属性移除。

参考

es5规范中文版

Standard ECMA-262

MDN:Symbol.toStringTag

ECMAScript 6 入门

谈谈 Object.prototype.toString 。

License

署名-非商业性使用-禁止演绎 4.0 国际 (CC BY-NC-ND 4.0)

最后,全部文章都会同步发送到微信公众号上,欢迎关注,欢迎提意见:

做者:对角另外一面

相关文章
相关标签/搜索