在 JavaScript 中,主要有七种基本数据类型Undefined、Null、Boolean、Number、String、Symbol、BigInt,还有一种复杂数据类型Object,其中包含了Data、function、Array、RegExp等。JavaScript 不支持任何创建自定义类型的机制,而所有值最终都将是上述8种数据类型之一。由于JavaScript 是一种动态类型语言,这意味着你可以在程序执行过程中改变变量的类型。
基本数据类型与引用数据类型的区别:基本数据类型是指存放在栈(stack)中的简单数据段,数据大小确定,内存空间大小可以分配,它们是直接按值存放的,所以可以直接按值访问引用类型是存放在堆(heap)内存中的对象,变量其实是保存的在栈内存中的一个指针(保存的是堆内存中的引用地址),这个指针指向堆内存。引用类型数据在栈内存中保存的实际上是对象在堆内存中的引用地址。通过这个引用地址可以快速查找到保存中堆内存中的对象。
那么,在实际工作代码开发中,有哪些方法去判断一个数据是哪种数据类型呢?这就是本篇文章要重点介绍的内容——JavaScript中的数据类型判断方法大全!
typeof 运算符是 JavaScript 的基础知识点,尽管它存在一定的局限性(见下文),但在前端js的实际编码过程中,仍然是使用比较多的类型判断方式。因此,掌握该运算符的特点,对于写出好的代码,就会起到很大的帮助作用。
typeof 运算符可能返回的类型字符串有:string, boolean, number, bigint, symbol, undefined, function, object。
// string
typeof '123'; // 'string'
typeof String(1); // 'string'
// boolean
typeof true; // 'boolean'
typeof Boolean(); // 'boolean'
// number
typeof 1; // number
typeof Number(10); // number
typeof NaN;//number
// undefined
typeof a; // undefined
typeof undefined; // undefined
// symbol
typeof Symbol(); // 'symbol'
typeof Symbol('foo'); // 'symbol'
typeof Symbol.iterator; // 'symbol'
// bigint
typeof 42n; // 'bigint'
typeof BigInt(1); // 'bigint'
// function
typeof function () { return 1 }; // function
typeof console.log; //function
typeof class cs {}; // 'function'
typeof String; // 'function'
typeof RegExp; // 'function'
typeof new Function(); // 'function'
// object
typeof null; // object
typeof []; // object
typeof [1,2,3]; // object
typeof {'id':11}; //object
typeof {}; //object
typeof Math; // 'object'
typeof new Number(1); // 'object'
需要注意的是typeof null返回为object,因为特殊值null被认为是一个空的对象引用。这是 JavaScript 语言的一个历史遗留问题。在JavaScript 最初的版本中,使用 32 位的值表示一个变量,其中前 3 位用于表示值的类型。000 表示对象,010 表示浮点数,100 表示字符串,110 表示布尔值,和其他的值都被认为是指针。在这种表示法下,null 被解释为一个全零的指针,也就是说它被认为是一个空对象引用,因此 typeof null的结果就是 "object"。
typeof 的局限性:在于无法精确判断出 null、数组、对象、正则 的类型。引用类型,除了function返回function类型外,其它均返回object。所以如果要精准判断,还需要使用其他技术手段,或组合判断(见下文)。
在 JS 的原型链和原型对象中,会通过 new 一个构造函数,来创建实例对象。构造函数的原型对象上会有一个 constructor 属性,指向了构造函数自身,所以实例对象通过原型链访问 constructor 属性,就能找到自己的构造函数,也就是自己的类型了。
new String('a').constructor === String; // true
"a".constructor === String; // true
(1).constructor === Number; // true
new Number(1).constructor === Number; // true
true.constructor === Boolean; // true
new Function().constructor === Function; // true
Symbol(0).constructor === Symbol; // true; // true
BigInt(1).constructor === BigInt; // true
new Error().constructor === Error; // true
[].constructor === Array; // true
new Date().constructor === Date; // true
new RegExp().constructor === RegExp; // true
new Object().constructor === Object; // true
注意:
null 和 undefined 是无效的对象,因此是不会有 constructor 存在的,这两种类型的数据需要通过其他方式来判断。
函数的 constructor 不一定准确,这个主要体现在自定义对象上,当开发者重写 prototype 后,原有的 constructor 引用会丢失,constructor 会默认为 Object。
String.prototype.constructor = function fn() {
return {};
}
console.log("前端技术营".constructor) // [Function: fn]
console.log("前端技术营".constructor === String) // false
instanceof 运算符用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上。也就是说检测实例对象是不是属于某个构造函数,可以用来做数据类型的检测。不能检测基本数据类型,只可用来判断引用数据。
// 不能检测基本数据类型
1 instanceof Number; // false
// 来检测引用数据
[] instanceof Array; // true
[] instanceof Object; // true
new Object() instanceof Object; // true
new Date() instanceof Date; // true
function Person(){}
new Person() instanceof Person; //true
Person instanceof Object; //true
new Date() instanceof Object; //true
new Person instanceof Object; // true
通过上面代码可以看到,instanceof 既能够判断出 [ ] 是Array的实例,又能判断出 [ ] 也是Object的实例,这是为什么呢?
通过《一文让你搞懂javascript中的构造函数、实例、原型、原型链,和它们之间的关系》这篇文章就可以明白其原因了。instanceof 能够判断出 [ ].__proto__ 指向 Array.prototype,而 Array.prototype.__proto__ 又指向了Object.prototype,最终Object.prototype.__proto__ 指向了null,标志着原型链的结束。因此,[ ]、Array、Object 就在内部形成了一条原型链。
缺点:
不能检测基本数据类型,只可用来判断引用数据,obj instanceof Type右操作数必须是函数或者 class。
2. 原型链可能被修改,导致检测结果不准确。
Object.prototype.toString
Object.prototype.toString——专业检测数据类型一百年! 它是一个专门检测数据类型的方法。
原理:当调用 Object.prototype.toString 时,JavaScript会将该调用传递给我们需要检查类型的对象。因为Object.prototype.toString是一个函数,所以传递给它的唯一参数是this,而且该参数是一个隐式参数。具体如下:Object.prototype.toString.call(obj),由于toString是Object.prototype上的方法,因此我们传递给它的参数应该是一个对象,而Object.prototype.toString方法本身却没有传递参数。这就是为什么我们需要用call将它与需要检查类型的对象连接起来。该方法可以指定函数内的this关键字上下文。因此,我们将检查类型的对象作为首个参数传递给了toString方法,并使用call方法将Object.prototype.toString作为一个函数来执行。这样JavaScript就会在上下文对象上执行Object.prototype.toString方法,从而返回一个表示该对象类型的字符串。
function checkDataType(data) {
return Object.prototype.toString.call(data).slice(8, -1);
}
checkDataType(1); // Number
checkDataType('a'); // String
checkDataType(true); //Boolean
checkDataType(null); //Null
checkDataType(undefined); //Undefined
checkDataType(Symbol('b')); //Symbol
checkDataType(BigInt(10)); //BigInt
checkDataType([]); //Array
checkDataType({}); //Object
checkDataType(function fn() {}); //Function
checkDataType(new Date()); //Date
checkDataType(new RegExp()); //RegExp
checkDataType(new Error()); //Error
checkDataType(document); //HTMLDocument
checkDataType(window); //Window
编写一个函数,对返回的字符串从第8位做一个截取,截取到倒数第一位,再去做类型比较。
Array.isArray() 检查传递的值是否为Array。它不检查值的原型链,也不依赖于它所附加的 Array 构造函数。对于使用数组字面量语法或 Array 构造函数创建的任何值,它都会返回 true。
Array.isArray() 也拒绝原型链中带有 Array.prototype,而实际不是数组的对象,但 instanceof Array 会接受。
// 下面的函数调用都返回 true
Array.isArray([]);
Array.isArray([1]);
Array.isArray(new Array());
Array.isArray(new Array("a", "b", "c", "d"));
Array.isArray(new Array(3));
// 鲜为人知的事实:其实 Array.prototype 也是一个数组:
Array.isArray(Array.prototype);
// 下面的函数调用都返回 false
Array.isArray();
Array.isArray({});
Array.isArray(null);
Array.isArray(undefined);
Array.isArray(17);
Array.isArray("Array");
Array.isArray(true);
Array.isArray(false);
Array.isArray(new Uint8Array(32));
// 这不是一个数组,因为它不是使用数组字面量语法或 Array 构造函数创建的
Array.isArray({ __proto__: Array.prototype });
JS 中有一个特殊的数字——NaN,表示 not a number,不是一个数字,但它却归属于数字类型。在上文中我们知道 typeof NaN 返回number。NaN 用于表示不是一个数字,它不等于任何值,包括它本身。ES6 提供了 Number.isNaN 方法,它能判断一个值是否严格等于NaN。
Number.isNaN(NaN); //true
Number.isNaN(1); //false
Number.isNaN('abc'); //false;
Number.isNaN([]); //false;
Number.isNaN({}); //false
上面的 Object.prototype.toString 方法,之所以对不同的数据类型,返回不同的标识字符串,就是因为 Symbol.toStringTag 。Symbol.toStringTag 是一个内置符号属性,它的值是一个字符串,用于表示一个对象的默认描述,也就是调用 Object.prototype.toString 会返回的内容。
const obj = {};
obj[Symbol.toStringTag] = 'ABCD';
console.log(Object.prototype.toString.call(obj)) // [object ABCD]
Symbol.toStringTag主要适用于需自定义类型的场景。
对于自定义对象,调用 Object.prototype.toString.call()方法,都只会返回 [object Object]。此时就可以使用 Symbol.toStringTag 来指定一个确定的类型了。
const arr = [];
Object.defineProperty(arr, Symbol.toStringTag, {
value: 'MyArray'
})
console.log(Object.prototype.toString.call(arr))
// [object MyArray]
Object.getPrototypeOf() 静态方法返回指定对象的原型,即内部 [[Prototype]] 属性的值。
Object.getPrototypeOf([]) === Array.prototype; // true
Object.getPrototypeOf({}) === Object.prototype; // true
Object.getPrototypeOf(function fn(){}) === Function.prototype; // true
Object.getPrototypeOf(new Date()) === Date.prototype; // true
Object.getPrototypeOf(new RegExp()) === RegExp.prototype; // true
Object.getPrototypeOf(new Error()) === Error.prototype; // true
isPrototypeOf() 是 Object函数(类)的下的一个方法,用于判断当前对象是否为另外一个对象的原型,如果是就返回 true,否则就返回 false。
let obj = new Object();
console.log(Object.prototype.isPrototypeOf(obj)); // true
obj对象是Object的实例,所以obj对象的原型(__proto__)指向Object的原型(prototype),上面会输出true。
function Human() {}
let human = new Human();
console.log(Human.prototype.isPrototypeOf(human)); // true
因为human对象是Human的实例,所以human对象的原型(__proto__)指向Human的原型(prototype),上面会输出true。
JavaScript中内置类Number、String、Boolean、Function、Array因为都是继承Object,所以下面的输出也都是true。
Object.prototype.isPrototypeOf(Number); // true
Object.prototype.isPrototypeOf(String); // true
Object.prototype.isPrototypeOf(Boolean); // true
Object.prototype.isPrototypeOf(Array); // true
Object.prototype.isPrototypeOf(Function); // true
另外值得一提的是 Function.prototype 也是Object的原型,因为Object也是一个函数(类),它们是互相生成的。
Object.prototype.isPrototypeOf(Function); // true
Function.prototype.isPrototypeOf(Object); // true
与instanceof的区别:instanceof 作用的原理就是判断实例的原型链中能否找到类的原型对象(prototype),而 isPrototypeOf 又是判断类的原型对象(prototype)是否在实例的原型链上。这两个表达的意思是一致的,之是写法不同而已。
instanceof的写法:A instanceof B,isPrototypeOf的写法:B.prototype.isPrototypeOf(A)。
直接通过与一个特定的值进行比较,从而判断数据的类型。主要适用undefined、 window、 document、 null 等。
const value = null;
console.log(value === null) // true
// 同时判断一个值是 undefined 或者 null
let value;
console.log(value == null) // true
如何判断当前脚本运行在浏览器还是node环境中?
this === window ? 'browser' : 'node'
通过判断Global对象是否为window,如果不为window,当前脚本没有运行在浏览器中。
本文整理总结了 JS 中常用的判断数据类型的方法,判断数据类型方法有很多,实际使用需要根据自己的需求使用最适合自己的方法。其中 typeof 和 Object.prototype.toString 使用场景是最多的,对一些特殊的数据类型,比如 null,NaN,自定义类型,可以选择其它的方式去进行判断,做到灵活运用。
该文章在 2024/4/19 18:05:26 编辑过