资讯专栏INFORMATION COLUMN

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

huangjinnan / 3370人阅读

摘要:规范对类型的判断进行了细化,前步可以看成跟的作用一样,获取到数据的类型,但是第步调用了的方法,如果再看规范的描述,可以知道这个其实是对象中的属性,如果这个属性返回的是一个字符串,则采用这个返回值作为数据的类型,否则才采用。

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

——几米

本文为读 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 时,会进行如下步骤:

如果 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 "]" 拼接而成的一个字符串。

规范对类型的判断进行了细化,前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)

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

作者:对角另一面

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/94004.html

相关文章

  • lodash源码分析数据类型获取的兼容性

    摘要:实例中构造函数的获取每个实例中都包含一个的属性,这个属性指向的是实例的构造函数,在获取到这个构造函数后,就可以调用它的方法,然后就可以比较了。 焦虑和恐惧的区别是,恐惧是对世界上的存在的恐惧,而焦虑是在我面前的焦虑。——萨特《存在与虚无》 本文为读 lodash 源码的第十九篇,后续文章会更新到这个仓库中,欢迎 star:pocket-lodash gitbook也会同步仓库的更新,...

    avwu 评论0 收藏0
  • lodash源码分析缓存方式的选择

    摘要:接口设计同样实现了跟一致的数据管理接口,如下依赖源码分析之缓存源码分析之缓存源码分析是否使用这个函数用来判断是否使用缓存。返回表示使用缓存,返回则使用或者缓存。获取对应缓存方式的实例这个函数根据来获取储存了该的缓存实例。 每个人心里都有一团火,路过的人只看到烟。——《至爱梵高·星空之谜》 本文为读 lodash 源码的第八篇,后续文章会更新到这个仓库中,欢迎 star:pocket-...

    HitenDev 评论0 收藏0
  • lodash源码分析缓存方式的选择

    摘要:接口设计同样实现了跟一致的数据管理接口,如下依赖源码分析之缓存源码分析之缓存源码分析是否使用这个函数用来判断是否使用缓存。返回表示使用缓存,返回则使用或者缓存。获取对应缓存方式的实例这个函数根据来获取储存了该的缓存实例。 每个人心里都有一团火,路过的人只看到烟。——《至爱梵高·星空之谜》 本文为读 lodash 源码的第八篇,后续文章会更新到这个仓库中,欢迎 star:pocket-...

    AdolphLWQ 评论0 收藏0
  • lodash源码分析isArguments

    摘要:卡尔维诺烟云本文为读源码的第二十一篇,后续文章会更新到这个仓库中,欢迎也会同步仓库的更新,地址依赖源码分析之数据类型获取的兼容性源码分析之源码分析用来判断某个值是否为类对象。如果某个值为类对象使用判断,并且调用返回的值为时,则为类对象。 有人命中注定要过平庸的生活,默默无闻,因为他们经历了痛苦或不幸;有人却故意这样做,那是因为他们得到的幸福超过了他们的承受能力。——卡尔维诺《烟云》 ...

    _Dreams 评论0 收藏0
  • lodash源码分析List缓存

    摘要:在之前的文章中已经介绍过,检测的是对应的数组在二维数组中的索引,其行为跟一致,不存在于二维数组中时,返回,否则返回索引值。最后将缓存数量减少。 昨日我沿着河岸/漫步到/芦苇弯腰喝水的地方顺便请烟囱/在天空为我写一封长长的信 潦是潦草了些/而我的心意/则明亮亦如你窗前的烛光/稍有暧昧之处/势所难免/因为风的缘故 ——洛夫《因为风的缘故》 本文为读 lodash 源码的第七篇,后续文章会...

    leon 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<