Javascript 数据类型检测及原理
前言
数据类型检测是 JavaScript 中既基础又考验原理的知识,如果你对 JavaScript 数据类型检测还不清楚,那么这篇文章应该会帮到你。
一、typeof
返回所属类型的字符串格式。但不能判断 object 类型下具体的数据结构,是普通对象、Array 或 Date 等。
// "number"
typeof 123
typeof NaN
typeof "123" // "string"
typeof true // "boolean"
typeof undefined // "undefined"
// "symbol"
typeof Symbol('a')
typeof Symbol.iterator
// "object"
typeof {a:1}
typeof [1,2,3]
typeof null
typeof new Date()
typeof new Number(1)
// "function"
function aaa(){}
typeof aaa
typeof new Function()
为什么 typeof null === "object"
在 JavaScript 最初实现中,JavaScript 是由一个表示类型的标签和实际数据值表示,对象的类型标签是 0。null 代表空指针,在大多数平台下值为 0x00,因此 null 的类型标签也成了0。
为什么 typeof new String('abc') === "object"
首先涉及到装箱转换的概念
每种基本数据类型在对象中都有对应的类。装箱转换就是将基本类型转换成对应的对象
new String('abc')
是将基本字符串装箱转换成字符串对象,所以返回"object"
。
我们可以通过 valueOf()
方法将字符串对象转换回基本字符串。
二、 instanceof
obj instanceof Foo
检测构造函数的 prototype 是否出现在对象原型链中的任何位置。
问题1:由于是使用原型链查找机制,所以不能检测基本数据类型。
问题2: Foo.prototype
的值和 obj.__proto__
都可能发生改变,导致检测结果不正确
function D(){}
function C(){}
let c = new C();
let d = new D()
c instanceof C // true
d instanceof D // true
// 修改 d.__proto__
Object.setPrototypeOf(d, C.prototype)
d instanceof D // false
d instanceof C // true
// 修改 C.prototype
C.prototype = {};
c instanceof C // false
扩展
使用 Symbol.hasInstance
可以自定义 instanceof 在某个类上的行为
class MyArray {
static [Symbol.hasInstance](instance) {
return Array.isArray(instance);
}
}
console.log([] instanceof MyArray); // true
三、Object.prototype.toString.call()
Symbol.toStringTag
Symbol.toStringTag
通常作为对象的属性使用,属性值是字符串类型,表示对象的自定义类型标签。Object.prototype.toString() 方法会读取这个标签,并包含在返回值中。
原理
这个方法会获取 this 对象的 [[class]]私有属性(ES6之后获取Symbol.toStringTag属性),然后包装进[object, ]返回
自定义的类,需要通过Symbol.toStringTag
属性设置类型标签后,才能返回正确的类
// 第一种情况
class Person{
get [Symbol.toStringTag](){
return 'Person'
}
}
let p = new Person();
Object.prototype.toString.call(p); // "[object Person]"
p.toString(); // "[object Person]"
// 第二种情况
class Person{
get [Symbol.toStringTag](){
return 'Person'
}
toString() {
return 'abc';
}
}
let p = new Person();
Object.prototype.toString.call(p); // "[object Person]"
p.toString() // "abc"
对比以上两种情况,实例调用toString
方法时,会先检测当前类的原型上是否有 toString()
方法,有则调用,没有则调用父类的toString()
方法。所以第一种情况返回的结果和Object.prototype.toString.call(p)
执行结果一致。
所以我们一般使用Object.prototype.toString.call()
来检测数据类型而不是通过实例直接调用 toString()
方法,是防止类重写 toString()
方法。