JS必备技能之数据类型判断与底层原理深入解析
作者:努力的小朱同学
一、简介
精准判断数据类型是每一位前端开发者的必备技能,但 JS 因存在基础类型与引用类型的区分以及历史遗留问题等,单一判断方式往往无法覆盖所有场景。本文梳理并解析了 5 类常用的数据类型判断方法,包括:基础类型常用的 typeof、对象类型专属的 instanceof 、全类型通用的 Object.prototype.toString.call()、基于构造函数引用的 constructor,以及针对数组、NaN、整数等场景的特定判断方案。
基础类型:
String、Number、Boolean、Null、Undefined、Symbol、bigint(ES2020新增)。
这些类型的数据占据的空间较小且大小是固定的,因此数据直接存储在栈(stack)内存中,方便频繁调用数据。
引用类型:
Object(包含Array、Function、Date、RegExp、Map、Set等等)。
该类型的数据占据的空间较大且大小不固定,是将数据存储在堆(heap)内存中,并在栈(stack)内存中存储一个引用指针,该指针指向当前数据在堆中的实际空间地址。
二、具体方案
1、typeof
typeof 运算符常用于获取基础数据的类型,可以获取到一个表示数据的类型的字符串,类型字符串有:number、string、boolean、object、function、undefined、symbol、bigint。
typeof 运算符在遇到除 null 之外基础类型的数据,都能正常获取其类型字符串(number、string等),而遇到 null 会返回 object ,这是由于历史遗留问题。但遇到对象类型数据,其判断结果具有局限性,遇到函数则会返回 function ,而遇到其他对象(如对象、数组、日期等)都只会返回 object,因此无法区分具体对象类型。
基本语法:
typeof 数据
判断原理:
JS中的数据,在底层中都是以二进制的形式存储的,并且会通过低位的 “类型标签”(tag) 来区分不同数据类型,不同的JS引擎具体实现不同,比如V8引擎中:二进制000结尾-表示对象类型、二进制1结尾-表示整数类型等等。typeof 的原理就是通过底层标签来判断数据的类型,而特殊的null,其所有二进制位都是 0 ,所以引擎会将以 000 结尾的标签将其误判为 object。
使用示例:
// 数字类型 typeof 37 === 'number'; typeof 3.14 === 'number'; typeof Math.LN2 === 'number'; typeof Infinity === 'number'; typeof NaN === 'number'; // 尽管NaN是非数字的意思 typeof Number(1) === 'number'; // 但不建议使用! // bigInt typeof 42n === 'bigint'; // 字符串类型 typeof "" === 'string'; typeof "bla" === 'string'; typeof (typeof 1) === 'string'; // typeof返回的是字符串 typeof String("abc") === 'string'; // 但不建议使用! // 布尔类型 typeof true === 'boolean'; typeof false === 'boolean'; typeof Boolean(true) === 'boolean'; // 但不建议使用! // Symbol类型 typeof Symbol() === 'symbol'; typeof Symbol('foo') === 'symbol'; typeof Symbol.iterator === 'symbol'; // Undefined typeof undefined === 'undefined'; var a // 声明但未赋值 typeof a === 'undefined'; typeof b === 'undefined'; // 未声明未赋值 // 对象类型 typeof {a:1} === 'object'; typeof [1, 2, 4] === 'object'; // 数组会被判断为对象 typeof new Date() === 'object'; typeof null === 'object'; // null 也会被判断为对象 // 函数 typeof function(){} === 'function'; typeof Math.sin === 'function';
2、instanceof
instanceof 运算符常用于判断对象数据的具体数据类型,可以判断一个对象数据是否为某个构造函数的实例,返回一个布尔值。
instanceof 运算符不适用于基础数据类型(包括null),但适用于基础类型的包装对象。并且如果判断数组、函数等具体对象数据是否为 Object 类型,结果也会是true,因为 Object 类型属于最外层的大类型。因此想要判断具体的对象类型,需要精准的判断,不能用排除法。
由于原型链是可以被修改的,因此判断结果并非绝对可靠。
基本语法:
对象 instanceof 构造函数
判断原理:
JS中对象数据存在原型链,instanceof 就是通过沿着左边对象的原型链向上查找,如果在左边对象的原型链上存在右边构造函数的 prototype ,则说明左边对象是右边构造函数的实例,也就是说左边对象属于右边构造函数的类型,返回 true,否则返回 false。
因此该方案只适用于同一个全局环境下的判断,因为在不同的全局环境(如iframe)中,构造函数的 prototype 指向的是不同的对象,会导致判断错误。
使用示例:
// 数组 var a = [1,2,3] console.log(a instanceof Array) // true console.log(a instanceof Object) // true,两者都成立 var a2 = new Array console.log(a2 instanceof Array) // true console.log(a2 instanceof Object) // true // 函数(包括箭头函数) var b = function() {} console.log(b instanceof Function) // true console.log((()=>{}) instanceof Function) // true console.log(b instanceof Object) // true // 对象 var c = {} console.log(c instanceof Object) // true console.log(Date instanceof Object) // true // 内置对象 var str = new String() str instanceof String // true var myDate = new Date() myDate instanceof Date; // true myDate instanceof Object; // true // 基础数据的包装类 const numObj = new Number(123); numObj instanceof Number; // true(包装对象可被判断) 123 instanceof Number; // false(原始值不可被判断)
3、Object.prototype.toString.call()
Object.prototype.toString.call() 方法可用于判断所有数据的具体类型,可以获取到一个表示数据具体类型的标准字符串,返回格式为: [object Xxxx]
,其中 Xxxx 就是数据的具体类型,无论是基本数据类型还是对象数据类型,都能精准的区分。
基本语法:
Object.prototype.toString.call(数据);
判断原理:
每个JS内置对象都有一个内部属性 [[Class]]
(抽象概念,无法直接访问),其值为该对象的类型标识(如 Array、Date、Number、String 等)。当调用 Object 原型上的 toString() 方法,并通过 call() 方法改变this的指向到目标数据时,对于对象类型数据,该方法会直接读取目标数据的 [[Class]]
属性,并返回对应类型字符串 [object [[Class]]]
;对于基本类型数据, call() 方法会先自动将其转换为对应的包装对象,再读取其 [[Class]]
属性,最后返回对应类型字符串 [object [[Class]]]
。
使用示例:
Object.prototype.toString.call(123); // "[object Number]" Object.prototype.toString.call('abc'); // "[object String]" Object.prototype.toString.call(true); // "[object Boolean]" Object.prototype.toString.call(null); // "[object Null]" Object.prototype.toString.call(undefined); // "[object Undefined]" Object.prototype.toString.call([1, 2]); // "[object Array]" Object.prototype.toString.call(new Date()); // "[object Date]" Object.prototype.toString.call(/abc/); // "[object RegExp]" Object.prototype.toString.call(function(){}); // "[object Function]" Object.prototype.toString.call({}); // "[object Object]" Object.prototype.toString.call(Symbol()); // "[object Symbol]" Object.prototype.toString.call(123n); // "[object BigInt]" Object.prototype.toString.call(new Map()); // "[object Map]" Object.prototype.toString.call(new Set()); // "[object Set]" Object.prototype.toString.call(new WeakMap()); // "[object WeakMap]"
4、constructor
constructor 常用于判断对象的具体类型,其是对象的一个属性,它指向创建该对象的构造函数,通过与特定的构造函数进行比较,区分对象的具体类型。
对于除 null、undefined 之外基础类型数据,在其调用 constructor 属性时,JS会自动进行“装箱”操作,将其临时变为对应的包装对象,从而拥有 constructor 属性;对于null、undefined,它们没有对应的包装对象,因此无法调用 constructor。
由于 constructor 属性是可以被修改的,因此判断结果并非绝对可靠。
基本语法:
数据.constructor === 构造函数
判断原理:
JS中每个对象在创建时都会关联一个构造函数,constructor 属性就是对这个构造函数的引用。例如,数组是由 Array 构造函数创建的,所以数组的 constructor 属性指向 Array;日期对象由 Date 构造函数创建,其 constructor 属性指向 Date。因此,通过比较对象的 constructor 与对应的构造函数,就能判断出对象的具体类型。
使用示例:
// 基础数据类型(除 null、undefined 外,通过包装对象实现) (123).constructor === Number; // true // 等价于 Number(123).constructor === Number; 'abc'.constructor === String; // true // 等价于 String('abc').constructor === String; true.constructor === Boolean; // true // 等价于 // 引用数据类型 [1, 2].constructor === Array; // true(可识别数组) new Date().constructor === Date; // true(可识别日期对象) ({}).constructor === Object; // true
5、特定类型判断
① Array.isArray()
判断数据是否为数组类型。
var arr = [] Array.isArray(arr); // true
② ===
准确判断 null 和 undefined。
let a = null; a === null; // true let b = undefined; b === undefined; // true a === b; // false
③ Number.isNaN()
判断数据是否为NaN。该方法不会将参数强制转换为数字类型,会直接判断传入的参数是否是 NaN,而全局的 isNaN() 方法会强制转换参数为数字,导致非NaN值被误判,因此更推荐使用 Number.isNaN()。
let n = NaN; Number.isNaN(n); // true
④ Number.isInteger()
判断数据是否为整数。
Number.isInteger(1); // true Number.isInteger(1.2); // false Number.isInteger(1.00); // true
三、 总结
没有万能的方法,只有最适合当前场景的方法。
判断方法 | 适用场景 | 关键局限 | 精准度 |
---|---|---|---|
typeof | 基础类型(除 null)、函数快速判断 | null 误判为 object,无法区分数组 / 日期等对象 | 基础类型高,引用类型低 |
instanceof | 引用类型(数组、日期等)的实例判断 | 不支持原始基础类型,原型链修改会导致误判 | 引用类型较高(需注意全局环境) |
Object.prototype.toString.call() | 所有类型(基础 + 引用)精准判断 | 语法相对繁琐,需处理返回的标准格式字符串 | 最高(全场景通用) |
constructor | 基础类型(除 null/undefined)、引用类型判断 | constructor 可被修改,null/undefined 无该属性 | 较高(需警惕属性篡改) |
特定判断 | 单一类型(数组、NaN、整数等)精准校验 | 适用范围窄,仅针对特定场景 | 极高(场景专属) |
总结
到此这篇关于JS必备技能之数据类型判断与底层原理的文章就介绍到这了,更多相关JS数据类型判断与底层原理内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!