深入理解你不知道的 typeof 和 Object.prototype.toString.call()

typeof

什么 typeof

在JavaScript中,typeof是一个操作符,用于检测一个值的数据类型,它可以检测出大部分的基本类型和引用类型,返回一个字符串表示该值的类型,可以检测 String、Object、undefined、Number、Function、Symbol、Bigint 类型

typeof 的使用

  • 我们来看一下 typeof 的使用,当然,可能下面的例子看完之后会有一些疑惑,我后面都会一一解析,案例如下:

    console.log(typeof 123) // number
    console.log(typeof '123') // string
    console.log(typeof false) // boolean
    console.log(typeof {}) // object
    console.log(typeof null) // object
    console.log(typeof undefined) // undefined
    console.log(typeof []) // object
    console.log(typeof Symbol()) // symbol
    console.log(typeof BigInt(123)) // bigint
    console.log(typeof function () {}) // function
    

typeof null 为什么是 “object”

为什么返回的是 “object”,这是 JavaScript 的一个历史遗留问题,在早期的 JavaScript 中,null 是用来给对象占位的,表示是一个空对象,而且对象在内存中的表示是以 32 位二进制数字的形式存储的,对象的在底层中二进制表示前三位都是 0,而在这种二进制的表示法中,null 的值会被存储为全是 0 的二进制数字,也因此当使用 typeof 检测 null 时,它会返回 “object”,表示 null 是一个特殊的空对象指针

typeof [] 为什么是 “object”

至于为什么检测数组时返回的是一个 “object” 呢,因为 typeof 在检测的这里检测是一种引用值,也就是说只要这里是引用类型就会返回 “object”,我们来测试一下,如下:

  • console.log('对象:', typeof {}) // 对象: object
    console.log('数组:', typeof []) // 对象: object
    console.log('Set:', typeof new Set()) // 对象: object
    console.log('Map:', typeof new Map()) // 对象: object
    console.log('Date:', typeof new Date()) // 对象: object
    console.log('RegExp:', typeof new RegExp()) // 对象: object
    
  • 其实看到这里你应该有一个疑惑,Function 不也是属于引用类型吗?就它特殊是吧,对,就它特殊

typeof function foo(){} 为什么是 “function”

在 JavaScript 中,函数是一种特殊的对象类型(函数对象),具有一些特殊的行为和属性,其中被区别于其他对象的点在于,函数是可以调用的,也因此函数和对象虽然都是引用类型,但是又因为具备这不同的属性和行为,函数在定义的时候就会被定义称为一种特殊的对象类型,即函数对象,所以为了区别函数对象类型和其他对象类型,JavaScript 在 typeof 运算符中将函数对象的类型识别为 "function"

  • 从上面的概述中我们可以总结出一个结论,当一个对象具备可调用的属性时,它就是一个函数对象,函数对象在被 typeof 检测时就会返回 function,我们可以来具体的检测一下,如图:
    在这里插入图片描述

  • 注意了,上面的这里我们并没有对他们进行 new 关键字的调用,因此就不会创建一个实例化的对象,也就是说 typeof 检测的是它们本身,它们本身为函数对象就会返回 “function”

  • 本着严谨的角度,我们在将上面例子中的函数对象进行实例化测试一下,如图:
    在这里插入图片描述

  • 这时候输出 object 是因为他们经过实例化之后已经是一个普通的函数对象

  • 这时候你可能会问那我在使用 typeof 检测 new Function() 的时候它返回也是 “function” 啊,这是因为 Function 这个构造函数它非常之特殊,特殊到什么地步呢,它在 JavaScript 运行之初就在内存当中了,我说我是 “function” 我就是 “function”,如果还要纠结这一点的我觉得你可能需要和语言创始人对线一手

typeof 总结

  1. 经过上述的解析,相信你对 typeof 有了更深刻的理解,现在我们也可以更好的解释一下为什么数组和对象被 typeof 检测时都是返回 “object”,因为函数对象经过 new 运算符的实例化之后,都会得到一个普通对象,虽然数组可以被索引值所获取,但是本身都是由一个函数对象实例化之后得到的普通的对象,只是这个对象更加具备一些特殊性而已
  2. 至于为什么 undefined 返回的是一个 “undefined”,这是因为 undefined 表示的是无值,是一种其他的数据类型
  3. typeof是一个一元运算符,它的作用是返回一个表示数据类型的字符串
  4. 需要注意的是,typeof运算符并不能准确地区分数组和普通对象,因为它们都被认为是 “object” 类型

Object.prototype.toString.call()

如果需要理解 Object.prototype.toString.call() 需要对 JavaScript 中原型链的知识有一定的理解,如果这方面不太了解的可以观看我的另一篇文档

文档地址:深入理解JavaScript之原型链

为什么 Object.prototype.toString.call() 可以判断数据类型

  1. 因为它利用了JavaScript的一个特性,每一个对象都有 toString() 方法,这个方法的作用是将对象转换为字符串类型,我们可以测试一下,如下:

    const obj = { a: 1, b: 2 }
    console.log(obj.toString()) // [object Object]
    console.log(typeof obj.toString()) // string
    
    const arr = ['aaa', 'bbb', 'ccc']
    console.log(arr.toString()) // aaa,bbb,ccc
    console.log(typeof arr.toString()) // string
    
    const str = 'Hello World'
    console.log(str.toString()) // Hello World
    console.log(typeof str.toString()) // string
    
    const flag = true
    console.log(flag.toString()) // true
    console.log(typeof flag.toString()) // string
    
  2. 通过上面举例的部分不知道你们有没有发现一个现象,好像只有 Object 使用 toString() 方法时返回了一个 [object Object],其余的数据类型都是返回了一个具体的字符串的值,而且好巧不巧这个 [object Object] 字符串这个字符串由"[object "和调用对象的类型名组成,表示是一个 Object 类型

  3. Object.prototype.toString() 这个方法会返回一个描述该对象的字符串,其中包含了"[object Object]"这样的具备数据类型的字符串,而当调用其他内置对象类型的 toString() 方法时,会根据具体的类型名返回相应的字符串

  4. 至于这个方法为什么可以返回一个具备数据类型的字符串,这是 JavaScript 内部的机制实现的,并不需要我们关心

为什么其他对象身上的 toString 方法不可以

  1. 按照我们上面的理论来说,那数组这些对象在调用 toString() 方法的时候也应该返回一个具备数据类型的字符串啊,为什么其他对象身上的 toString() 方法不能判断数据类型呢,因为其他对象对于这个 toString() 方法进行了重写,也就是说在这些对象身上的原型上面又增加了一个 toString() 方法,然后根据原型链的就近原则查找顺序就会找到对象本身身上的 toString() 方法,我们可以例举一些常见的对象身上的 toString() 方法,如下:

    1. 数字类型(Number):toString() 方法可以将一个数字转换成字符串形式,还可以指定进制,比如:二进制、十六进制
    2. 字符串类型(String):toString() 方法返回字符串本身
    3. 布尔类型(Boolean):toString() 方法返回 true 或 false 字符串
    4. 数组类型(Array):toString() 方法将数组转换成一个由数组元素组成的字符串,每个元素用逗号分隔
    5. 对象类型(Object):toString() 方法返回"[object Object]"字符串
    6. 函数类型(Function):toString() 方法返回函数的源代码
    7. 日期类型(Date):toString() 方法返回日期时间的字符串形式
    8. 正则表达式类型(RegExp):toString() 方法返回正则表达式的源代码
  2. 具体是不是我们所推测的一样呢,我们可以使用方法 hasOwnProperty() 方法来检测这些对象原型上是否具备 toString() 方法,检测结果如图:
    在这里插入图片描述

  3. 原型只有函数对象身上有,普通对象身上是没有的,因此如果我们将 Array 原型身上的 toString() 方法去掉的话,那 Array 使用 toString() 方法时发现原型上没有,然后原型对象又是一个普通对象,这个普通对象的隐式原型__proto__又指向 Object 函数对象的原型对象身上,此时发现 Object 的原型身上具有 toString() 方法,就会直接使用,而使用 Object 身上的 toString() 方法就可以判断数据类型,刚好符合我们的需求,我们可以检测一下是否真的是这样呢,如下:

    delete Array.prototype.toString
    const arr = [1, 2, 3]
    console.log(arr.toString()) // [object Array]
    
  4. 可以看到输出了我们所需要的数据类型,如果大家有兴趣还可以测试一下其他对象的打印结果,我这里就不在演示了

为什么需要 call 方法

  1. 经过上面的解析我们已经可以得出为什么 Object.prototype.toString() 方法可以判断数据类型了,为什么还需要 call 呢,我们可以不加 call 测试一下数组,看看是否能得到正确的结果,如下:

    console.log(Object.prototype.toString([])) // [object Object]
    
  2. 为什么输出的是 “[object Object]” ,这是因为 Object.prototype 本身就是一个原型对象,而原型对象就是一个普通对象,所以当然会输出 “[object Object]”

  3. 也因此我们需要使用 call 方法来帮我们改变这个调用者,也就是改变 this 执行,关于这块的知识就不在进行讲解了,大家自行了解即可,我们可以测试一下,如下:

    console.log(Object.prototype.toString.call([])) // [object Array]
    console.log(Object.prototype.toString.call(123)) // [object Number]
    console.log(Object.prototype.toString.call(function foo() {})) // [object Function]
    console.log(Object.prototype.toString.call('123')) // [object String]
    
  4. 现在就可以精准的判断出我们的数据类型了,而我们前面删除一个对象身上的 toString() 方法不也正是为了让一个数组在调用 toString() 方法的时候是使用 Object 原型上的 toString() 方法,而非自己本身的 toString() 方法,因此使用 call 方法可以直接帮助我们省去删除自身原型身上 toString() 方法这一步骤,算是一个另类的语法糖吧

总结

现在我相信你对于 Object.prototype.toString.call() 为什么可以判断数据类型就有很好的认知了,而非是只知其用,不知其源,而当我们需要判断一个数据类型的时候每次书写这样一段代码就显得很呆板也繁琐,因此我们可与封装一个函数,来帮助我们实现,如下

// 当 slice() 方法的第一个参数是正数,第二个参数是负数时
//  - 第一个参数表示从字符串开头开始的索引位置
//  - 第二个参数表示从字符串末尾开始的索引位置
function checkType(any) {
	return Object.prototype.toString.call(any).slice(8, -1)
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值