lodash源码分析之获取数据类型
所有的悲伤,总会留下一丝欢乐的线索,所有的遗憾,总会留下一处完美的角落,我在冰峰的深海,寻找希望的缺口,却在惊醒时,瞥见绝美的阳光!
——几米
本文为读 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)
最后,所有文章都会同步发送到微信公众号上,欢迎关注,欢迎提意见: 
作者:对角另一面
lodash源码分析之获取数据类型的更多相关文章
- lodash源码分析之自减的两种形式
这个世界需要一个特定的恶人,可以供人们指名道姓,千夫所指:"全都怪你". --村上春树<当我谈跑步时我谈些什么> 本文为读 lodash 源码的第六篇,后续文章会更新到 ...
- lodash源码分析之缓存方式的选择
每个人心里都有一团火,路过的人只看到烟. --<至爱梵高·星空之谜> 本文为读 lodash 源码的第八篇,后续文章会更新到这个仓库中,欢迎 star:pocket-lodash gitb ...
- lodash源码分析之数组的差集
外部世界那些破旧与贫困的样子,可以使我内心世界得到平衡. --卡尔维诺<烟云> 本文为读 lodash 源码的第十七篇,后续文章会更新到这个仓库中,欢迎 star:pocket-lodas ...
- lodash源码分析之List缓存
昨日我沿着河岸/漫步到/芦苇弯腰喝水的地方 顺便请烟囱/在天空为我写一封长长的信 潦是潦草了些/而我的心意/则明亮亦如你窗前的烛光/稍有暧昧之处/势所难免/因为风的缘故 --洛夫<因为风的缘故& ...
- lodash源码分析之缓存使用方式的进一步封装
在世界上所有的民族之中,支配着他们的喜怒选择的并不是天性,而是他们的观点. --卢梭<社会与契约论> 本文为读 lodash 源码的第九篇,后续文章会更新到这个仓库中,欢迎 star:po ...
- lodash源码分析之baseFindIndex中的运算符优先级
我悟出权力本来就是不讲理的--蟑螂就是海米:也悟出要造反,内心必须强大到足以承受任何后果才行. --北岛<城门开> 本文为读 lodash 源码的第十篇,后续文章会更新到这个仓库中,欢迎 ...
- lodash源码分析之compact中的遍历
小时候, 乡愁是一枚小小的邮票, 我在这头, 母亲在那头. 长大后,乡愁是一张窄窄的船票, 我在这头, 新娘在那头. 后来啊, 乡愁是一方矮矮的坟墓, 我在外头, 母亲在里头. 而现在, 乡愁是一湾浅 ...
- UiAutomator源码分析之获取控件信息
根据上一篇文章<UiAutomator源码分析之注入事件>开始时提到的计划,这一篇文章我们要分析的是第二点: 如何获取控件信息 我们在测试脚本中初始化一个UiObject的时候通常是像以下 ...
- lodash源码分析之chunk的尺与刀
以不正义开始的事情,必须用罪恶使它巩固. --莎士比亚<麦克白> 最近很多事似乎印证了这句话,一句谎言最后要用一百句谎言来圆谎. 本文为读 lodash 源码的第二篇,后续文章会更新到这个 ...
随机推荐
- IE浏览器右键菜单插件开发(下篇)——如何用c#安装、卸载IE右键插件
建立Installer安装类,如图: 代码如下: using Microsoft.Win32; using ResourceShare.UserClient.Common; using System; ...
- sqlserver存储过程及临时表在统计中的应用
use ResourceShare --统计使用情况 alter PROCEDURE StaSheryUse @start datetime, @end datetime, @orgId int AS ...
- c#多线程同步之EventWaitHandle使用
有这么一个场景,我需要借助windows剪贴板把数据插入到word域中. 实现步骤: 1.把剪贴板数据保存到变量. 2.使用剪贴板实现我们的业务. 3.把变量里的数据存回剪贴板. 但是结果却令人诧异, ...
- UWP:使用Composition实现类似安卓的水波纹Ripple效果
先放效果图: 首先,建立一个RippleHelper.cs文件,然后建立以下附加属性: IsFillEnable:是否扩大到整个控件 RippleDuration:持续时间 RippleRadius: ...
- 记一次 synchronized 锁字符串引发的坑兼再谈 Java 字符串
业务有一个需求,我把问题描述一下: 通过代理IP访问国外某网站N,每个IP对应一个固定的网站N的COOKIE,COOKIE有失效时间. 并发下,取IP是有一定策略的,取到IP之后拿IP对应的COOKI ...
- 设计模式——外观模式(C++实现)
#include <string> #include <iostream> using namespace std; class STSystemA { public: voi ...
- Spring事务传播机制和数据库隔离级别
Spring事务传播机制和数据库隔离级别 转载 2010年06月26日 10:52:00 标签: spring / 数据库 / exception / token / transactions / s ...
- [Oracle] UNIX与Windows 2000上Oracle的差异(II)
作者:Ian Adam & David Stien, SAIC Ltd 日期:19-Dec-2003 出处:http://www.dbanotes.net翻译:Fenng 数据库启动与关闭 在 ...
- java 日期格式处理
Java 日期时间 Date类型,long类型,String类型表现形式的转换 1.java.util.Date类型转换成long类型java.util.Date dt = new Date();Sy ...
- Markdown 语法手册 (完整整理版)
http://blog.csdn.net/witnessai1/article/details/52551362