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

——几米

本文为读 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 方法,采用如下步骤:

  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]] 的规定:

本规范的每种内置对象都定义了 [[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 时,会进行如下步骤:

  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)

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

作者:对角另一面

lodash源码分析之获取数据类型的更多相关文章

  1. lodash源码分析之自减的两种形式

    这个世界需要一个特定的恶人,可以供人们指名道姓,千夫所指:"全都怪你". --村上春树<当我谈跑步时我谈些什么> 本文为读 lodash 源码的第六篇,后续文章会更新到 ...

  2. lodash源码分析之缓存方式的选择

    每个人心里都有一团火,路过的人只看到烟. --<至爱梵高·星空之谜> 本文为读 lodash 源码的第八篇,后续文章会更新到这个仓库中,欢迎 star:pocket-lodash gitb ...

  3. lodash源码分析之数组的差集

    外部世界那些破旧与贫困的样子,可以使我内心世界得到平衡. --卡尔维诺<烟云> 本文为读 lodash 源码的第十七篇,后续文章会更新到这个仓库中,欢迎 star:pocket-lodas ...

  4. lodash源码分析之List缓存

    昨日我沿着河岸/漫步到/芦苇弯腰喝水的地方 顺便请烟囱/在天空为我写一封长长的信 潦是潦草了些/而我的心意/则明亮亦如你窗前的烛光/稍有暧昧之处/势所难免/因为风的缘故 --洛夫<因为风的缘故& ...

  5. lodash源码分析之缓存使用方式的进一步封装

    在世界上所有的民族之中,支配着他们的喜怒选择的并不是天性,而是他们的观点. --卢梭<社会与契约论> 本文为读 lodash 源码的第九篇,后续文章会更新到这个仓库中,欢迎 star:po ...

  6. lodash源码分析之baseFindIndex中的运算符优先级

    我悟出权力本来就是不讲理的--蟑螂就是海米:也悟出要造反,内心必须强大到足以承受任何后果才行. --北岛<城门开> 本文为读 lodash 源码的第十篇,后续文章会更新到这个仓库中,欢迎 ...

  7. lodash源码分析之compact中的遍历

    小时候, 乡愁是一枚小小的邮票, 我在这头, 母亲在那头. 长大后,乡愁是一张窄窄的船票, 我在这头, 新娘在那头. 后来啊, 乡愁是一方矮矮的坟墓, 我在外头, 母亲在里头. 而现在, 乡愁是一湾浅 ...

  8. UiAutomator源码分析之获取控件信息

    根据上一篇文章<UiAutomator源码分析之注入事件>开始时提到的计划,这一篇文章我们要分析的是第二点: 如何获取控件信息 我们在测试脚本中初始化一个UiObject的时候通常是像以下 ...

  9. lodash源码分析之chunk的尺与刀

    以不正义开始的事情,必须用罪恶使它巩固. --莎士比亚<麦克白> 最近很多事似乎印证了这句话,一句谎言最后要用一百句谎言来圆谎. 本文为读 lodash 源码的第二篇,后续文章会更新到这个 ...

随机推荐

  1. c#抽取pdf文档标题(2)

    public class IETitle { public static List<WordInfo> WordsInfo = new List<WordInfo>(); pr ...

  2. python感悟

    嗯,对于python我其实也是一知半解,比较擅长java,我就拿java和python进行比较一下吧 1,首先先说简单的程序运行时间问题,对于同等时间复杂度的测试程序,python虽然运行速度没有c快 ...

  3. MongoDB 桌面管理器MongoVUE

    MongoVUE是一个桌面GUI工具,专用于Windows平台,它有一个简洁.清爽的界面,它的基本功能是免费的.它可以以文本视图.树视图.表格视图来显示MongoDB的数据.还可以保持查询的结果供以后 ...

  4. CSS3字体发光效果

    text-shadow 该属性为文本添加阴影效果 text-shadow: h-shadow v-shadow blur color; h-shadow: 水平阴影的位置(阴影水平偏移量),可为负值, ...

  5. Mybatis+Mysql插入数据库返回自增主键id值的三种方法

    一.场景: 插入数据库的值需要立即得到返回的主键id进行下一步程序操作 二.解决方法: 第一种:使用通用mapper的插入方法 Mapper.insertSelective(record): 此方法: ...

  6. 【OpenCV-Python:实现人脸、人眼、嘴巴识别】实战(一)

    AI时代的到来,手机上的APP开始应用人脸识别去完成事情,如iphoneX的人脸解锁,百度自动贩卖机的人脸识别系统进行支付,支付宝的人脸识别登录等,提高了使用软件的易用性,但也因为其便利性,在某些市面 ...

  7. Filecoin协议(挖矿)

    Filecoin协议是如何运作的? 用户如何使用Filecoin系统来查询,存储数据? 存储矿工如何挖矿,如何获取FIL代币? 检索矿工如何挖矿,获取FIL代币? 本文主要回答上面的这些问题!!! 首 ...

  8. Junit test使用

    1.导入maven依赖 <dependency> <groupId>junit</groupId> <artifactId>junit</arti ...

  9. react 实用的性能优化方式

    react 组件渲染分为初始化渲染和更新渲染,当我们更新某个组件的时候,只是想关键路径上组件的render,但react的默认做法是调用所以组件的reder,再生成虚拟dom进行对比,如不变则不进行更 ...

  10. linux --> gcc编译之路径搜索

    gcc编译之路径搜索 头文件 --> 搜寻先从-I开始; --> 找gcc的环境变量 : C_INCLUDE_PATH,CPLUS_INCLUDE_PATH,OBJC_INCLUDE_PA ...