在js中判断数据类型通常有四种方式:
- typeof
- Object.prototype.toString
- instanceof
- constructor
下面逐步分析它们之间的异同
typeof
console.log(typeof 1) // number
console.log(typeof '1') // string
console.log(typeof true) // boolean
console.log(typeof Symbol()) // Symbol
console.log(typeof undefined) // undefined
console.log(typeof null) // object
console.log(typeof []) // object
console.log(typeof {}) // object
console.log(typeof new RegExp('/A/')) // object
console.log(typeof function(){}) // function
对于基础类型,typeof可以判断出除null以外的所有基础类型 对于引用类型,函数以外的都会被判断成object 因此,typeof并不能准确的判断出一个数据的类型
对于引用类型,我们来看另一种方式:
Object.prototype.toString
console.log(Object.prototype.toString.call(1)) // [object Number]
console.log(Object.prototype.toString.call('1')) // [object String]
console.log(Object.prototype.toString.call(true)) // [object Boolean]
console.log(Object.prototype.toString.call(Symbol())) // [object Symbol]
console.log(Object.prototype.toString.call(undefined)) // [object Undefined]
console.log(Object.prototype.toString.call(null)) // [object Null]
console.log(Object.prototype.toString.call([])) // [object Array]
console.log(Object.prototype.toString.call({})) // [object Object]
console.log(Object.prototype.toString.call(new RegExp('/A/'))) // [object RegExp]
console.log(Object.prototype.toString.call(function(){})) // [object Function]
这样看的话,Object.prototype.toString似乎能准确判断出所有的数据类型 但是需要考虑到这样一种情况:
class A {}
let a = new A()
console.log(Object.prototype.toString.call(a)) // [object Object]
这种方式无法判断出自定义的类型
要准确判断引用类型,我们再看第三种方式:
instanceof
class A {}
let a = new A()
console.log(a instanceof A) // true
console.log([] instanceof Array) // true
console.log([] instanceof Object) // true
// 等价于
console.log([].__proto__ === Array.prototype)
console.log([].__proto__.__proto__ === Object.prototype)
从上面的例子可以看出:instanceof 就是通过判断右侧的值的原型是否在左侧的原型链上
下面,实现自己的instanceof方法
function myInstanceof(A, B) {
A = A.__proto__
B = B.prototype
while(true) {
if (A == null) {
return false
}
if (A === B) {
return true
}
A = A.__proto__
}
}
class A {}
let a = new A()
console.log(myInstanceof(a, A)) // true
console.log(myInstanceof([], Array)) // true
不建议直接使用__proto__来访问原型,可采用js原生提供的Object.getPrototypeOf()方法改造
遗憾的是,instanceof无法判断基础类型,所以js原生提供的判断方式都无法精确的判断类型
但是官方提供了一个API,可以改造我们的instanceof
Symbol.hasInstance用于判断某对象是否为某构造器的实例。因此你可以用它自定义 instanceof 操作符在某个类上的行为。
换句话说就是可以自己定义instanceof的行为,前面我们知道了typeof可以准确判断基础类型 下面,来实现可以判断基础类型的instanceof
class ValidataStr {
static [Symbol.hasInstance](x) {
return typeof x === 'string'
}
}
console.log('1' instanceof ValidataStr) // true
constructor
"abc".constructor == String // true
(123).constructor == Number // true
(true).construcotr == Boolean // true
[].constructor == Array // true
({}).constructor == Object // true
(function() {}).constructor = Function // true
下面来实现一个通用的类型判断函数,可以判别出基础类型和引用类型
function getType(obj) {
const type = typeof obj
// 如果是基础类型,直接返回
if (type !== 'object') {
return type
}
return Object.prototype.toString.call(obj).replace(/^\[object (\S+)\]$/, '$1')
}
最后来看一个对类型判断综合应用的手写题:手写JSON.stringify方法
function jsonStringify(data) {
let type = typeof data;
if (type !== 'object') {
let result = data;
//data 可能是基础数据类型的情况在这里处理
if (Number.isNaN(data) || data === Infinity) {
//NaN 和 Infinity 序列化返回 "null"
result = "null";
} else if (type === 'function' || type === 'undefined' || type === 'symbol') {
// 由于 function 序列化返回 undefined,因此和 undefined、symbol 一起处理
return undefined;
} else if (type === 'string') {
result = '"' + data + '"';
}
return String(result);
} else if (type === 'object') {
if (data === null) {
return "null"
// 第01讲有讲过 typeof null 为'object'的特殊情况
} else if (data.toJSON && typeof data.toJSON === 'function') {
return jsonStringify(data.toJSON());
} else if (data instanceof Array) {
let result = [];
//如果是数组,那么数组里面的每一项类型又有可能是多样的
data.forEach((item, index) => {
if (typeof item === 'undefined' || typeof item === 'function' || typeof item === 'symbol') {
result[index] = "null";
} else {
result[index] = jsonStringify(item);
}
});
result = "[" + result + "]";
return result.replace(/'/g, '"');
} else {
// 处理普通对象
let result = [];
Object.keys(data).forEach((item, index) => {
if (typeof item !== 'symbol') {
//key 如果是 symbol 对象,忽略
if (data[item] !== undefined && typeof data[item] !== 'function' && typeof data[item] !== 'symbol') {
//键值如果是 undefined、function、symbol 为属性值,忽略
result.push('"' + item + '"' + ":" + jsonStringify(data[item]));
}
}
});
return ("{" + result + "}").replace(/'/g, '"');
}
}
}
总结:
- js原生判断数据类型的方式有四种:typeof、 instanceof、 Object.prototype.toString、constructor
- typeof 可以精确判断出基础类型,但是无法准确判断出具体的引用类型,null会被判断为object
- Object.prototype.toString 可以精确判断出基础类型和引用类型,但是无法判断自定义的类型
- instanceof 是通过判断右侧的值的原型是否在左侧的原型链上实现的,可以判断出自定义类型,但是无法判断基础类型,可以通过Symbol.hasInstance来自定义instanceof的行为从而实现对基础类型的判断
- constructor通过找到构造函数来判断类型