类型判断
typeof
typeof返回操作数的类型名称字符串,它没什么原理不原理的,返回值是直接在规范中给出来的,如下表:
| 类型 | 结果 |
|---|---|
| Undefined | "undefined" |
| Null | "object" |
| Boolean | "boolean" |
| Number | "number" |
| String | "string" |
| Symbol | "symbol" |
| BigInt | "bigint" |
| Function | "function" |
| 其它引用类型 | "object" |
| class | "function" |
instanceof
obj instanceof Constructor@return Boolean
过去instanceof主要用于判断对象的类型,其原理是通过原型链查找obj.__proto__是否等于类型构造函数的原型Constructor.prototype
function _instanceof(obj, constructor) {
if (obj === null) {
return false;
}
// 判断原型是否存在
if (constructor && constructor.prototype) {
var _prototype = Object.getPrototypeOf
? Object.getPrototypeOf(obj)
: obj.__proto__;
if (_prototype === constructor.prototype) {
return true;
} else {
_instanceof(_prototype, constructor.prototype);
}
}
return false;
}
ES6 以后,通过Symbol.hasInstance可以定义instanceof的判断结果,instanceof在执行的时候会优先获取Symbol.hasInstance的值,并将其转换称Boolean类型返回。
class Person {
static [Symbol.hasInstance]() {
return true;
}
}
//这个被改造的类使用instanceof,无论什么都会返回true
console.log(undefined instanceof Person); //true
//判断class是不是使用new调用的
function _instanceof(this, myClass) {
if (typeof Symbol !== 'undefined' && myClass[Symbol.hasInstance]) {
return !!myClass[Symbol.hasInstance](this);
}
}
Object.prototype.toString
根据 ES 规范文档的描述,早期的 ES 规范设计的有一个[[Class]]属性,用作各种内置对象的标称类型标签,只能通过Object.prototype.toString这个方法获取[[Class]]的字符串值。
Object.prototype.toString是目前为止兼容性最强,类型判断最准确的方式,它连 Promise 都能判断(靓仔狂喜);根据 ES 规范文档的描述,toString.call(this)被调用会做以下处理:
- 判断 this 是不是
undefined,是返回"[object Undefined]"; - 判断 this 是不是
null,是返回"[object Null]"; - 对 this 进行
ToObject转换,得到包装的对象; - 获取包装对象的
Symbol.toStringTag属性,如果存在并且值是String类型,则返回其值和字符串"[object"连接的结果; - 如果不存在
Symbol.toStringTag属性,且为内置对象,返回类型名称和字符串"[object"连接的结果; - 如果都不满足,最后返回
[object Object]
var type = Object.prototype.toString.call(window);
console.log(type); //[object Window]
var type = Object.prototype.toString.call(new Date());
console.log(type); //[object Date]
var type = Object.prototype.toString.call(null);
console.log(type); //[object Null]
var type = Object.prototype.toString.call(undefined);
console.log(type); //[object Undefined]
function Foo() {}
var foo = new Foo();
var type = Object.prototype.toString.call(foo);
console.log(type); //[object Object]
可以用正则表达式处理一下结果,只显示类型字符串
function _typeof(obj) {
return Object.prototype.toString
.call(obj)
.replace(/^\[object\s(\w+)\]$/, '$1')
.toLowerCase();
}
从 ES6 开始,规范定义了Symbol.toStringTag属性,程序中可以直接使用Symbol.toStringTag为类型定义名称,执行Object.prototype.toString的过程也发生了变化,会优先根据Symbol.toStringTag属性进行返回值的判断,如果没有再根据对象的私有属性判断其类型。
Array.prototype[Symbol.toStringTag] = 'test';
console.log(Object.prototype.toString.call(new Array())); // [object test]
Symbol.toStringTag
ES6 新增的内置对象的Symbol.toStringTag属性值可以返回内置对象类型的字符串,它返回的内容是规范定义的,并不是调用Object.prototype.toString这个方法,注意不能搞混淆了。
JSON[Symbol.toStringTag]:'JSON'Math[Symbol.toStringTag]:'Math'- Module 对象
M[Symbol.toStringTag]:'Module' ArrayBuffer.prototype[Symbol.toStringTag]:'ArrayBuffer'DataView.prototype[Symbol.toStringTag]:'DataView'Map.prototype[Symbol.toStringTag]:'Map'Promise.prototype[Symbol.toStringTag]:'Promise'Set.prototype[Symbol.toStringTag]:'Set'%TypedArray%.prototype[Symbol.toStringTag]:'Uint8Array'等WeakMap.prototype[Symbol.toStringTag]:'WeakMap'WeakSet.prototype[Symbol.toStringTag]:'WeakSet'%MapIteratorPrototype%[Symbol.toStringTag]:'Map Iterator'%SetIteratorPrototype%[Symbol.toStringTag]:'Set Iterator'%StringIteratorPrototype%[Symbol.toStringTag]:'String Iterator'Symbol.prototype[Symbol.toStringTag]:'Symbol'Generator.prototype[Symbol.toStringTag]:'Generator'GeneratorFunction.prototype[Symbol.toStringTag]:'GeneratorFunction'
不过这里单独要讲一下Date类型,如果直接访问一个Date类型对象的Symbol.toStringTag属性,得到的结果是undefined,这里是因为规范根本没有给Date类型定义Symbol.toStringTag这个属性,可以参考这个回答 —— 为什么 Date 的 Symbol.toStringTag 返回的是 undefined ? - 知乎 (zhihu.com)
如果是自定义的对象类型,使用这个属性为创建的类定义类型名称,弥补toString的不足。
class Person {
get [Symbol.toStringTag]() {
return 'Person';
}
}
var o = new Person();
console.log(Object.prototype.toString.call(o)); //[object Person]
Object()
当Object作为函数单独调用时,实际上发生的就是装箱的ToObject操作,将除了Null和Undefined类型外的原始值包装成对象,但是对于引用类型的Object,它都是直接返回这个值,所以可以利用Object和===判断一个值是不是引用类型。
Object(obj) === obj; //true
其它类型内置的判断方法
Array.isArray()判断数组
Array.isArray()其实也是利用了toString的实现方式
if (!Array.isArray) {
Array.isArray = function(arg) {
return Object.prototype.toString.call(arg) === '[object Array]';
};
}
判断对象是否为引用类型
function isObject(obj) {
if (obj !== null && (typeof obj === 'object' || typeof obj === 'function')) {
return true;
}
return false;
}