Javascript 类型判断方法
Javascript 常见的类型判断方法主要有以下 5 种:
- typeof 操作符
- === 严格相等操作符
- instanceof 操作符
- constructor 属性
- Object.prototype.toString.call()
下面我会对 Javascript 数据类型进行一个简单的介绍,然后再对五种类型判断方法展开分析与讨论。
1.Javascript 数据类型
首先大家要知道 Javascript 是动态类型语言,所以定义变量时不用指定类型,这意味着相同的变量在不同时刻可能属于不同的类型。 本文只对数据类型进行展开,并不做详细解释,大家可以自行参考MDN 官网 JavaScript 数据类型。
1.1 原始数据类型
- Number 数字类型
- String 字符类型
- Boolean 布尔类型
- Undefined 类型
- Null 类型
- Symbol 符号类型(ES6 新增)
- Bigint 类型(ES6 新增)
1.2 引用数据类型
引用数据类型可以概括为Object对象类型,但其实 Object 可以细分为许多子类,常见的有以下 8 种。
- Array 数组类型
- Function 函数类型
- Error 错误类型
- Date 日期类型
- RegExp 正则类型
- Set、WeakSet 类型(ES6 新增)
- Map、WeakMap 类型(ES6 新增)
- Promise 对象(ES6 新增)
2.方法一:typeof A
//原始类型:除 Null 之外, typeof值都是对应的类型
console.log(typeof 2); // number
console.log(typeof "str"); // string
console.log(typeof true); // boolean
console.log(typeof undefined); // undefined
console.log(typeof null); // object
console.log(typeof Symbol(1)); // symbol
console.log(typeof BigInt(1)); // bigint
// 引用类型:除了 Function 对象实例, typeof值都是 object
console.log(typeof new Object()); // object
console.log(typeof new Array()); // object
console.log(typeof new Function()); // function
console.log(typeof new Error("err")); // object
console.log(typeof new RegExp()); // object
console.log(typeof new Date()); // object
console.log(typeof new Set()); // object
console.log(typeof new Map()); // object
console.log(typeof new Promise((resolve, reject) => resolve("data"))); // object
2.1 typeof 注意点
- null 类型返回 object(历史遗留问题,详情可看参考博客 1)
- Function 类型返回 function
2.2 typeof 方法总结
由上面的运行结果可见 typeof 方法:
- 对于原始数据类型:除了 Null 之外,其他类型判断都准确。
- 对于引用数据类型:除了 Function 之外,其他 Object 子类型需使用其他判断方法来进行类型区分。
3.方法二:A === B
let myNull = null;
console.log(myNull === null); // true
let myUndefined;
console.log(myUndefined === undefined); // true
3.1 === 方法总结
- === 方法的类型判断能力较弱
- 只可用于判断 null 与 undefined 类型,用于弥补 typeof null = object 的缺点。
4.方法三:object instanceof constructor
instanceof 运算符用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上。
// 数据类型无法检测
console.log(2 instanceof Number); // false
console.log(true instanceof Boolean); // false
console.log("str" instanceof String); // false
let myUndefined;
console.log(undefined instanceof undefined); // TypeError
let myNull = null;
console.log(myNull instanceof null); // TypeError
console.log(Symbol(1) instanceof Symbol); // false
console.log(1n instanceof BigInt); // false
// 可以检测内置对象类型
console.log({} instanceof Object); // true
console.log([] instanceof Array); // true
console.log(function () {} instanceof Function); // true
console.log(new Error("err") instanceof Error); // true
console.log(/1/ instanceof RegExp); // true
console.log(new Date() instanceof Date); // true
console.log(new Set() instanceof Set); // true
console.log(new Map() instanceof Map); // true
console.log(
new Promise((resolve, reject) => resolve("data")) instanceof Promise
); // true
但 instanceof 只能判断两个对象是否属于实例关系,所以存在判断不准确的缺点。 如以下代码:
function myFun() {}
console.log(myFun instanceof Object); // true
console.log(myFun instanceof Function); // true
// new myFun() 是 myFun 的一个实例
console.log(new myFun() instanceof foo); // true
console.log(new myFun() instanceof Object); // true
console.log(new myFun() instanceof Function); // true
4.1 instanceof 方法总结
由上面的运行结果可见 instanceof 方法存在以下缺点:
- instanceof 无法判断原始数据类型。(null 和 undefined 无法使用,会报错)
- instanceof 能判断引用数据类型,但由于原型链的存在,所以不能很准确的判断出数据类型。
5.方法四:A.constructor === B
使用 constructor 方法之前,大家要有原型对象三角图的概念:
- 每个对象都有一个 __proto__ 属性,该属性指向自己的原型对象
- 每个构造函数都有一个 prototype 属性,该属性指向实例对象的原型对象
- 原型对象里的 constructor 指向构造函数本身
如下图所示:
所以当一个实例对象访问 constructor 属性时,由于自身没有该属性,所以会顺着原型链去访问其原型对象的 constructor 属性。
// 原始数据类型
console.log((2).constructor === Number); // true
console.log("str".constructor === String); // true
console.log(true.constructor === Boolean); // true
//由于 null 和 undefined 没有构造函数
console.log(undefined.constructor); // TypeError
console.log(null.constructor); // TypeError
console.log(Symbol(1).constructor === Symbol); // true
console.log(2n.constructor === BigInt); // true
//引用数据类型
console.log({}.constructor === Object); // true
console.log([].constructor === Array); // true
console.log(function () {}.constructor === Function); // true
console.log(new Error("err").constructor === Error); // true
console.log(/1/.constructor === RegExp); // true
console.log(new Date().constructor === Date); // true
console.log(new Set().constructor === Set); // true
console.log(new Map().constructor === Map); // true
console.log(
new Promise((resolve, reject) => resolve("data")).constructor === Promise
); // true
但如果创建对象前修改了构造函数的原型,会导致 constructor 方法不可靠。 如下代码:
function myFun() {}
Object.setPrototypeOf(myFun, Array.prototype);
console.log(myFun.constructor === Function); // false
console.log(myFun.constructor === Array); // true
5.1 constructor 方法总结
由上面的运行结果可见 constructor 方法存在以下缺点:
- 原始数据类型中由于 null 和 undefined 没有构造函数,因此不存在原型链,无法使用 constructor 方法,这两种类型需使用其他方式来判断。
- constructor 方法不稳定,当重写对象的 prototype 后,原有的 constructor 引用会被改变。(默认为 Object)
6.方法五:Object.prototype.toString.call()
每个对象都有一个 toString() 方法,默认情况下,toString() 方法被每个 Object 对象继承。如果此方法在自定义对象中未被覆盖,toString() 返回 "[object type]" 。(type 是对象的类型)
//原始数据类型
console.log(Object.prototype.toString.call(123)); //[object Number]
console.log(Object.prototype.toString.call("123")); //[object String]
console.log(Object.prototype.toString.call(true)); //[object Boolean]
console.log(Object.prototype.toString.call(undefined)); //[object Undefined]
console.log(Object.prototype.toString.call(null)); //[object Null]
console.log(Object.prototype.toString.call(undefined)); //[object Undefined]
console.log(Object.prototype.toString.call(null)); //[object Null]
console.log(Object.prototype.toString.call(Symbol(1))); //[object Symbol]
console.log(Object.prototype.toString.call(2n)); //[object BigInt]
//引用数据类型
console.log(Object.prototype.toString.call({})); //[object Object]
console.log(Object.prototype.toString.call([])); //[object Array]
console.log(Object.prototype.toString.call(function () {})); //[object Function]
console.log(Object.prototype.toString.call(new Error("err"))); //[object Error]
console.log(Object.prototype.toString.call(/1/)); // [object RegExp]
console.log(Object.prototype.toString.call(new Date())); // [object Date]
console.log(Object.prototype.toString.call(new Set())); // [object Set]
console.log(Object.prototype.toString.call(new Map())); // [object Map]
console.log(
Object.prototype.toString.call(
new Promise((resolve, reject) => resolve("data"))
)
); //[object Promise]
由于 Object.prototype.toString.call()方法调用起来比较麻烦,而且返回结果不好判断。 所以我们可对该方法进行简单的封装,方便后续调用。
let getType = (target) => Object.prototype.toString.call(target).slice(8, -1);
getType({}); //Object
getType([]); //Array
6.1 Object.prototype.toString.call() 方法总结
由上面的运行结果可见:
- Object.prototype.toString.call() 方法能准确判断所有数据类型,我们只需对其进行简单的封装,就可得到一个调用简便、返回结果清晰的类型判断方法。
另外我们还可以借助第三方库封装好的 API 来进行类型判断,如:Jquery 中 的 type API。
7.全文总结
- typeof 方法可以判断除 null 之外的原始数据类型。
- === 方法只可用于判断 null 与 undefined。
- instanceOf 和 constructor 方法,涉及到了原型链,由于自定义对象的原型链可被改变,所以这两种方法不太稳定。
- Object.prototype.toString.call() 方法是判断数据类型的最好的方法。
有了数据类型判断的知识,我们就可以尝试着手写深浅拷贝函数,接下来我会更新实现深浅拷贝函数。
参考博客:
结语
这是我目前所了解的知识面中最好的解答,当然也有可能存在一定的误区。
所以如果对本文存在疑惑,可以在评论区留言,我会及时回复的,欢迎大家指出文中的错误观点。
最后码字不易,觉得有帮助的朋友点赞、收藏、关注走一波。