今日前端小知识——判断数据类型的几种方法
判断数据类型的方法
JavaScript 是一种弱类型或者说动态语言,在使用一个变量时无需提前声明变量的类型,在程序运行过程中,类型会被自动确定,这意味着可以使用同一个变量保存不同类型的数据。
JavaScript 中数据类型主要分为两种:基本数据类型和引用数据类型。基本数据类型包括 Number、Boolean、String、undefined、null、Symbol、BigInt,引用数据类型包括 Object、Function、Array、Date、Map、Set 等多种对象。实际开发中拿到一个数据可能需要先对数据的类型进行判断,这里总结了几种常用的方法以及对应的优缺点。
typeof
typeof 操作符返回一个字符串,表示未经计算的操作数的类型。语法如下。
typeof operand / typeof(operand)
typeof 能判断出以下 8 种类型:Number、Boolean、String、undefined、Symbol、BigInt、Object、Function。需要注意的几点:
- typeof null === ‘object’
- typeof NaN === ‘number’
在 JavaScript 最初的实现中,JavaScript 中的值是由一个表示类型的标签和实际数据值表示的。对象的类型标签是 0。由于 null 代表的是空指针(大多数平台下值为 0x00),null 的类型标签是 0,typeof null 也因此返回 “object”。
typeof(1); // number
typeof(1.3); // number
typeof(NaN); // number
typeof(true); // boolean
typeof(''); // string
typeof('hi'); // string
typeof(`hhh`); // string
// 已声明但未赋值的变量
let one;
typeof(one); // undefined
// 未声明的变量
typeof(none); // undefined
typeof(undefined); // undefined
typeof(13n); // bigint
typeof(Symbol(13)); // symbol
let obj = {
name: 'ostuthere'
}
typeof(obj); // object
let nowTime = new Date();
typeof(nowTime); // object
let arr = [1, 2, 3];
typeof(arr); // object
typeof(null); // object
let func1 = function(){
console.log('hello');
}
function func2(){
console.log('hello');
}
typeof(func1); // function
typeof(func2); // function
优点:除了 null,其它基本数据类型都能准确地判断
缺点:function 之外的其它引用数据类型、基础数据类型 null 以及使用构造函数创建的 Number 、String、Boolean 等都被判断为 object
instanceof
instanceof 运算符可以判断一个对象(object)是否为某个构造函数(constructor)的实例,如果是就返回 true,否则返回 false。原理是判断构造函数的 prototype 属性是否出现在某个实例对象的原型链上,一般用来判断引用数据类型。语法如下。
object instanceof constructor
var arr1 = new Array(2);
arr1 instanceof Array; // true
arr1 instanceof Object; // true
var arr2 = [];
arr2 instanceof Array; // true
var str1 = new String('123');
str1 instanceof String; // true
str1 instanceof Object; // true
var str2 = '1234';
// str2 是基本数据类型,不是 String 构造函数的实例对象,不存在原型链,所以返回 false
str2 instanceof String; // false
let num1 = new Number('123');
num1 instanceof Number; // true
num1 instanceof Object; // true
let num2 = 13;
num2 instanceof Number; // false
function A(name, age){
this.name = name;
this.age = age;
}
let p1 = new A('ostuthere', 18);
p1 instanceof A; // true
p1 instanceof Object; // true
p1 instanceof A 返回 true 并不代表一直都会是 true,因为 A.prototype 有可能被改变,改变后的 prototype 不一定会出现在 p1 的原型链上。
// 改变构造函数的原型之后,原来创建的实例会被判断为 false
A.prototype = {
sayHi: function(){
console.log('hi');
}
}
p1 instanceof A; // false
function B(name, age, gender){
this.name = name;
this.age = age;
this.gender = gender;
}
p1 instanceof B; // false
B.prototype = new A(); // 继承
let p2 = new B('Bob', 20, 'male');
p2 instanceof B; // true
// A.prototype 出现在了 p2 的原型链上,所以返回 true
p2 instanceof A; // true
优点:能够检测出引用数据类型
缺点:无法判断基本数据类型;由于构造函数的 prototype 能够更改,可能导致判断失误;不能跨 iframe 或 window(即不同的全局环境)
constructor
在 JavaScript 中,每个具有原型的对象都会自动获得 constructor 属性,它指向构造函数。除了 arguments、Enumerator、Error、Global、Math、RegExp、Regular Expression 等一些特殊对象外,其他所有的 JavaScript 内置对象都具备 constructor 属性,例如:Array、Boolean、Date、Function、Number、Object、String 等,注意基本数据类型(不包括 null 和 undefined)的 constructor 属性只读。由于 null 和 undefined 是无效对象,没有 constructor 属性,无法通过该方法判断。
let obj1 = {};
obj1.constructor === Object; // true
let obj2 = new Object;
obj2.constructor === Object; // true
let arr1 = [];
arr1.constructor === Array; // true
let arr2 = new Array;
arr2.constructor === Array // true
let num1 = new Number(13);
num1.constructor === Number; // true
let num2 = 13;
num2.constructor === Number; // true
num2.constructor; // ƒ Number() { [native code] }
除了基本数据类型的 constructor 只读之外,对象的 constructor 是可以被更改的,如下
function typeFun(){}
num1.constructor;
// 将通过 Number 构造函数创建的实例对象 num1 的 constructor 指向自定义函数 typeFun
num1.constructor = typeFun;
num1.constructor === Number; // false
num1.constructor === typeFun; // true
优点:能够检测出引用数据类型,还可以检测出 undefined 和 null 之外的基本数据类型
缺点:由于 constructor 所指向的构造函数是可以被修改的,可能导致判断失误;不能跨 iframe 或 window(即不同的全局环境)
Object.prototype.isPrototypeOf()
isPrototypeOf() 方法用于测试一个对象(prototypeObj)是否存在于另一个对象(object)的原型链上,若是则返回 true,否则返回 false。语法如下。
prototypeObj.isPrototypeOf(object)
isPrototypeOf() 与 instanceof 运算符不同。在表达式 "object instanceof Function"中,object 的原型链是针对 Function.prototype 进行检查的,而不是针对 Function 本身。
function funA() {}
function funB() {}
function funC() {}
funB.prototype = Object.create(funA.prototype);
funC.prototype = Object.create(funB.prototype);
let obj1 = new funC();
funC.prototype.isPrototypeOf(obj1); // true
funB.prototype.isPrototypeOf(obj1); // true
funA.prototype.isPrototypeOf(obj1); // true
Object.prototype.isPrototypeOf(obj1); // true
与 instanceof 类似,不能检测基本数据类型,且由于 prototype 能被修改导致检测结果不一定真实。
Object.prototype.toString.call()(最推荐使用)
Object.prototype.toString() 方法默认返回"[object type]",其中 type 是调用者的数据类型。从原型链的角度来说,所有对象最终都会指向 Object 对象,依据 JS 的变量查找规则,所有对象都能访问到 Object 的 toString() 方法,所以我们可以通过 toString() 来获取每个对象的类型。但实际上,不少对象都改写了 toString 方法。
BigInt(13).toString(); // '13'
let num = new Number(1)
num.toString(); // '1'
let arr = new Array(1,2,3)
arr.toString(); // '1,2,3'
为了保证每个对象都能通过 Object.prototype.toString() 方法来检测类型,我们需要通过 Object.prototype.toString.call() 或者 Object.prototype.toString.apply() 的形式来调用,第一个参数是要检查的对象。
Object.prototype.toString.call(1); // [object Number]
Object.prototype.toString.call(true); // [object Boolean]
Object.prototype.toString.call('13'); // [object String]
Object.prototype.toString.call(undefined); // [object Undefined]
Object.prototype.toString.call(null); // [object Null]
Object.prototype.toString.call(Symbol(13)); // [object Symbol]
Object.prototype.toString.call(13n); // [object BigInt]
Object.prototype.toString.call([1,2,3]); // [object Array]
Object.prototype.toString.call(new Function()); // [object Function]
Object.prototype.toString.call(new Date()); // [object Date]
Object.prototype.toString.call(new RegExp()); // [object RegExp]
Object.prototype.toString.call(new Error()); // [object Error]
Object.prototype.toString.call(window) ; // [object global] window是全局对象global的引用
优点:能够检测出所有的数据类型
缺点:在 IE6 之前,这两者的类型都被判定为 Object(应该没人用这么旧的浏览器了吧)
补充知识点:instanceof 与 isPrototypeOf() 的区别
A instanceof B:A是对象,B是构造函数。判断构造函数 B 的原型是否在对象 A 的原型链上。
A.isPrototypeOf(B):A 与 B 都是对象。判断对象 A 是否出现在对象 B 的原型链上。
接下来手动实现 instanceof 和 isPrototypeOf() 并进行验证,感受一下两者的区别。
// 实现 instanceof
function myInstanceof(obj, _constructor){
// 判断 obj 是否为基本数据类型
// 判断 obj.__proto__ === _constructor.prototype
let basicType = ['number', 'boolean', 'string', 'undefined', 'symbol', 'bigint'];
let typeOfObj = typeof(obj);
if (typeOfObj === null || basicType.includes(typeOfObj)) {
return false;
}
let L = obj.__proto__;
let R = _constructor.prototype;
while (L) {
if (L === R) {
return true;
} else if (L && L.__proto__) {
L = L.__proto__;
} else {
return false;
}
}
}
console.log(myInstanceof([1, 2, 3], Array)); // true
console.log(myInstanceof(13, Number)); // false
// 实现 isPrototypeOf()
Object.prototype.myIsPrototypeOf = function (obj) {
// 判断 obj 是否为基本数据类型
// 判断 obj.__proto__ === this (当前调用对象)
let basicType = ['number', 'boolean', 'string', 'undefined', 'symbol', 'bigint'];
let typeOfObj = typeof(obj);
if (typeOfObj === null || basicType.includes(typeOfObj)) {
return false;
}
let L = obj.__proto__;
while (L) {
if (L === this) {
return true;
} else if (L && L.__proto__) {
L = L.__proto__;
} else {
return false;
}
}
}
let p = {
msg: 'hello'
}
let o = Object.create(p);
console.log(p.myIsPrototypeOf(o)); // true
console.log(Object.prototype.myIsPrototypeOf(o)); // true
console.log(Number.prototype.myIsPrototypeOf(1)); // false
通过上面的代码我们可以发现,instanceof 和 isPrototypeOf() 都会查找原型链,看目标对象是否存在于指定对象的原型链上。但前者查找的目标对象是构造函数的 prototype,后者所查找的目标对象就是函数调用者本身。