前言
JS的变量与其他语言的变量有很大区别,因为其变量松散的本质,决定了变量只是在特定时间内用于保存特定值的一个名字而已,变量的值及其数据类型可在声明周期内改变。
JS的数据类型可分为基本类型和引用类型,先简单介绍两种数据类型,再来分析判断数据类型的几种方法。当然,这个也是大厂常考的面试题,同学们可按照文章的思路进行回答和扩展,让面试官耳目一新。
数据类型
基本类型
基本类型包括Undefined、Null、String、Number、Boolean、Symbol。基本类型按值访问,所以我们可操作保存在变量中实际的值。
基本类型的值在内存中占据固定大小的空间,是被保存在栈内存中。从一个变量向另一个变量复制基本类型的值,会创建这个值的一个副本,这两个值完全独立地存放在栈内存中。
引用类型
引用类型是对象类型,包括Object、Array、Function、Data、Regexp、Error。引用类型的值是保存在堆内存中的对象,JS不允许直接访问内存中的位置,也就是说不能直接访问操作对象的内存空间。
操作对象时,实际上是在操作对象的引用,所以说引用类型的值是按引用访问的。从而有[1, 2] === [1, 2]为false。
判断数据类型
简单的讲完JS的两种数据类型,接下来介绍一下JS判断数据类型的4种方法。
typeof
typeof是确定一个变量是string、number、boolean、symbol(ES6新增类型)还是undefined的最佳工具。注意,这里并没有提及null以及引用型数据。
typeof可能返回下面某个结果,结果的对应值如下:
- undefined:未定义的值
- string:字符串
- number:数值
- boolean:布尔
- symbol:唯一值
- object:对象或空值(
null) - function:函数
typeof undefined; // undefined
typeof null; // object
typeof "这是一段字符串"; // string
typeof 1; // number
typeof true; // boolean
typeof new Symbol(); // symbol
typeof new Object(); // object
typeof new Function(); // function
typeof new Date(); // object
上面的例子中,对于基本类型来说,除开null都可返回正确的结果。调用typeof null会返回object,是因为null被认为是一个空的对象引用,因此返回了object,当然这个也是JS设计语言早期遗留的Bug。
而在其他引用类型,除开function均返回object类型,因此用typeof来判断引用类型数据的类型并不可取,typeof适合用来判断基础类型值。
instanceof
instanceof可用来判断一个实例对象是否属于一个构造函数,其表达式A instanceof B,如果A是B的实例,则返回true,否则返回false。
实现原理其实就是在A的原型链上寻找是否有原型等于B.prototype,如果一直找到A原型链的顶端null,仍然找不到原型等于B.prototype,那么就可返回false。原型链的知识可戳往期文章《原型与原型链的拷问》回顾下哦,这里就不再讲原型链啦~
new Date() instanceof Date; // true
new Date() instanceof Object; // true
[] instanceof Array; // true
[] instanceof Object; // true
function Person() {};
const person = new Person();
person instanceof Person; // true
person instanceof Object; // true
从上面的例子可看到,instanceof可判断出[]是Array的实例,Date对象是Date的实例,person是Person构造函数的实例,到这里并没什么问题,但是instanceof认为这些也都是Object的实例,这就有点令人疑惑。
其实可根据instanceof的实现原理来分析一下,上面已经讲过实现原理,在这里我们套用一下instanceof用于数组判断的过程。
[] instanceof Array,因为能找到[].__proto__指向Array.prototype,因此返回true。[] instanceof Object,在这里就是也是要沿着[]的原型链找,有[].__proto__指向Array.prototype,又因为Array.prototype默认是Object的实例,所以有Array.prototype.__proto__指向了Object.prototype,因此这就是为什么instanceof认为[]也是Object的实例。
instanceof只能用来判断两个对象是否属于实例关系,并不能判断一个对象属于什么类型。简单说,就是判断两个类是否从属关系。
instanceof的问题在于,假如只有一个全局执行环境,如果网页中有两个框架,实际上就存在两个不用的全局执行环境,从而存在两个不同版本的Array构造函数。如果从一个框架向另一个框架传入一个数组,那么传入的数组与第二个框架中原生创建的数组分别是不同的构造函数。
const iframe = document.createElement("iframe");
document.body.appendChild(iframe);
const IArray = window.frames[0].Array;
const iarr = new IArray();
iarr instanceof Array; // false
Array.isArray(iarr); // true
为了解决这个问题,ES5新增了Array.isArray(),这个方法能确定某个值是不是数组或类数组。
constructor
上面提到的原型链,原型对象的constructor属性指向了构造函数,又因为实例对象的__proto__属性指向原型对象,因此可有:每一个实例对象都可通过constructor来访问它的构造函数。而JS内置对象在内部构建时也是这么做的,因此可用来判断数据类型。
"".__proto__.constructor === String; // true
// 下面将属性__proto__去掉,效果相同
"".constructor === String; // true
new Number(1).constructor === Number; // true
true.constructor === Boolean; // true
[].constructor === Array; // true
new Date().constructor === Date; // true
new Function().constructor === Function; // true
可看出,大部分类型都能通过这个属性来判断。但是由于undefined和null是无效的对象,因此是没有constructor属性的,这两个值不能用这种方法判断。另外,当重写原型时,原型原有的constructor会丢失,这时判断也就不生效了。
function Person() {};
Person.prototype = {
name: "XX"
};
const person = new Person();
person.constructor === Person; // false
这时打印person.constructor,可看到是一个Object。为什么会变成Object呢?这是因为在重新定义原型时,传入的是一个对象{},{}是new Object()的字面量,因此会将Object原型上的constructor传递给{},所以person.constructor也就打印出了Object。
因此,在重写原型对象时,都需要给constructor重新赋值,来保证对象实例的类型不改变。这个点在开发时记得记得注意!
toString
Object.prototype.toString方法返回对象的类型字符串,因此可用来判断一个值的类型。因为实例对象有可能会自定义toString方法,会覆盖Object.prototype.toString,所以在使用时,最好加上call。会有以下返回值
[object Undefined]:未定义的值[object Null]:空值[object String]:字符串[object Number]:数值[object Boolean]:布尔[object Symbol]:唯一值[object Object]:对象[object Array]:数组[object Function]:函数[object Date]:日期[object RegExp]:正则[object Error]:错误
Object.prototype.toString.call(undefined); // [object Undefined]
Object.prototype.toString.call(null); // [object Null]
Object.prototype.toString.call("这是字符串"); // [object String]
Object.prototype.toString.call(1); // [object Number]
Object.prototype.toString.call(true); // [object Boolean]
Object.prototype.toString.call({}); // [object Object]
Object.prototype.toString.call([]); // [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]
总结与对比
typeof使用简单,但是只适用于判断基础类型数据instanceof能判断引用类型,不能检测出基本类型,且不能跨iframe使用constructor基本能判断所有类型,除了null和undefined,但是constructor容易被修改,也不能跨iframe使用toString能判断所有类型,因此可将其封装成一个全能的DataType()判断所有数据类型
function DataType(tgt, type) {
const dataType = Object.prototype.toString.call(tgt).replace(/\[object (\w+)\]/, "$1").toLowerCase();
return type ? dataType === type : dataType;
}
DataType("young"); // "string"
DataType(20190214); // "number"
DataType(true); // "boolean"
DataType([], "array"); // true
DataType({}, "array"); // false
总结
JS的四种判断方法都有各自的优点跟缺点,要根据具体情况采取合适的判断方式。那么就到这里啦,有什么写得不对还麻烦各位大佬指出。有你们的支持我还会继续写出更好的文章~