JavaScript 数据类型详解:分类、种类、判断方法及深浅差异
在 JavaScript 编程中,数据类型是构建所有程序的基础,理解它的分类、判断方式以及不同类型的核心差异,是写出健壮代码、避免隐蔽 Bug 的关键。本文将全面拆解 JS 数据类型的相关知识点,从分类到实操,帮你彻底吃透这一基础核心内容。
一、JS 数据类型的分类与具体种类
JavaScript 数据类型分为两大类别,共计8 种具体类型,这一划分是基于数据的存储方式、访问机制的本质差异而来。
1. 两大核心分类
- 基础数据类型(也叫原始数据类型、值类型)
- 引用数据类型(也叫复杂数据类型、引用类型)
2. 8 种具体数据类型
(1)基础数据类型(7 种)
基础数据类型是不可再分的最小数据单元,直接存储具体的值,具体包括:
String:字符串类型,用于表示文本数据,如"Hello JS"、'123'(单引号、双引号、反引号均可包裹);Number:数值类型,包含整数、浮点数以及特殊数值,如100、3.14、NaN(非数字)、Infinity(无穷大);Boolean:布尔类型,仅包含两个值,用于表示真假状态:true(真)和false(假);Undefined:未定义类型,只有一个值undefined,表示变量已声明但未赋值,或访问不存在的对象属性时的返回值;Null:空值类型,只有一个值null,表示一个有意指向的 “空对象”,通常用于手动清空变量的引用;Symbol:ES6 新增的唯一标识类型,每个 Symbol 实例都是独一无二的,用于解决对象属性名冲突问题,如Symbol('name');BigInt:ES10 新增的大整数类型,用于表示超出Number类型安全范围(±2^53-1)的整数,后缀以n标识,如9007199254740993n。
(2)引用数据类型(1 种,核心为 Object)
引用数据类型本质是对数据的引用,存储的是内存地址,而非具体值,核心类型为Object,常见的衍生 / 内置类型都属于Object的子类:
- 普通对象:
{ name: "张三", age: 20 }; - 数组:
[1, 2, 3, "JS"](本质是有序索引的对象); - 函数:
function fn() { return 1; }(本质是可执行的对象); - 正则表达式:
/^\d+$/; - 日期对象:
new Date()等。
二、判断 JavaScript 数据类型的常用方法
在开发中,我们经常需要判断变量的数据类型,不同方法有各自的适用场景和局限性,以下是 4 种核心判断方式:
1. typeof 操作符:基础数据类型首选
typeof是最常用的基础判断方法,返回一个表示数据类型的字符串,语法:typeof 变量/值。
适用场景与示例
javascript
运行
// 基础数据类型判断(除 null 外均准确) console.log(typeof "abc"); // "string" console.log(typeof 123); // "number" console.log(typeof true); // "boolean" console.log(typeof undefined); // "undefined" console.log(typeof Symbol("id")); // "symbol" console.log(typeof 123n); // "bigint" // 引用数据类型判断(局限性:均返回 "object" 或 "function") console.log(typeof {}); // "object" console.log(typeof []); // "object" console.log(typeof /abc/); // "object" console.log(typeof function(){}); // "function"(唯一能准确区分的引用类型) // 经典坑:null 会返回 "object" console.log(typeof null); // "object"局限性
- 无法区分
null和普通对象,对null会误判为"object"; - 无法区分数组、正则、普通对象等引用类型,均返回
"object"。
2. instanceof 操作符:判断引用类型的具体子类
instanceof基于原型链判断,用于检测一个对象是否是某个构造函数的实例,语法:变量 instanceof 构造函数。
适用场景与示例
javascript
运行
// 区分不同引用类型 const arr = [1, 2, 3]; const obj = { name: "张三" }; const fn = function() {}; const reg = /^\d+$/; const date = new Date(); console.log(arr instanceof Array); // true console.log(obj instanceof Object); // true console.log(fn instanceof Function); // true console.log(reg instanceof RegExp); // true console.log(date instanceof Date); // true // 基础数据类型返回 false(除了通过构造函数创建的包装对象) console.log(123 instanceof Number); // false console.log(new Number(123) instanceof Number); // true局限性
- 无法判断基础数据类型(包装对象除外,不推荐使用包装对象);
- 原型链可被修改,可能导致判断结果失真;
- 无法跨窗口 / 跨框架判断(不同窗口的构造函数原型不同)。
3. Object.prototype.toString.call ():万能判断方法
这是最准确、最通用的判断方式,通过调用 Object 原型上的toString方法,返回格式为"[object 类型名]"的字符串,能精准区分所有数据类型。
适用场景与示例
javascript
运行
// 基础数据类型全准确 console.log(Object.prototype.toString.call("abc")); // "[object String]" console.log(Object.prototype.toString.call(123)); // "[object Number]" 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(Symbol("id"))); // "[object Symbol]" console.log(Object.prototype.toString.call(123n)); // "[object BigInt]" // 引用数据类型全准确 console.log(Object.prototype.toString.call([])); // "[object Array]" console.log(Object.prototype.toString.call({})); // "[object Object]" console.log(Object.prototype.toString.call(function(){})); // "[object Function]" console.log(Object.prototype.toString.call(/abc/)); // "[object RegExp]" console.log(Object.prototype.toString.call(new Date())); // "[object Date]"优势与注意事项
- 优势:万能适配,无盲区,判断结果最精准;
- 注意:必须通过
call()改变this指向,直接调用toString()会根据对象自身的方法返回结果(如数组调用toString()会返回数组元素拼接字符串)。
4. 特殊判断方法:针对特定类型
除了上述通用方法,还有一些针对性的判断方式:
Array.isArray():专门判断数组,比instanceof更可靠(可跨窗口判断),如Array.isArray([1,2,3]) // true;Number.isNaN():专门判断是否为真正的NaN(弥补typeof NaN返回"number"的缺陷),如Number.isNaN(NaN) // true,Number.isNaN("123") // false。
三、基础数据类型与引用数据类型的核心区别
基础数据类型和引用数据类型的差异,本质源于存储位置、访问方式的不同,具体体现在以下 5 个核心方面:
1. 存储位置不同
- 基础数据类型:存储在 ** 栈内存(Stack)** 中,栈内存具有容量小、访问速度快、先进后出的特点,直接存储变量的具体值;
- 引用数据类型:实际的数据内容存储在堆内存(Heap)中(堆内存容量大、访问速度较慢),而变量本身在栈内存中存储的是指向堆内存中数据的内存地址(引用地址),相当于给堆内存中的数据贴了一个 “地址标签”。
2. 赋值 / 传参机制不同
- 基础数据类型:采用值传递,赋值或传参时,会创建一份值的副本,新旧变量 / 函数参数与原变量互不影响,修改其中一个不会改变另一个;
javascript
运行
let a = 10; let b = a; // 复制 a 的值,b 是独立副本 b = 20; console.log(a); // 10(a 不受 b 修改影响) - 引用数据类型:采用引用传递,赋值或传参时,复制的是栈内存中的引用地址,新旧变量 / 函数参数指向堆内存中的同一个数据,修改其中一个会影响所有指向该地址的变量;
javascript
运行
let obj1 = { name: "张三" }; let obj2 = obj1; // 复制引用地址,obj1 和 obj2 指向同一个堆内存对象 obj2.name = "李四"; console.log(obj1.name); // "李四"(obj1 受 obj2 修改影响)
3. 可变性不同
- 基础数据类型:不可变(Immutable),基础类型的值一旦创建就无法修改,我们平时对字符串、数字的 “修改”,本质是创建了一个新的值,而非修改原有的值;
javascript
运行
let str = "hello"; str.toUpperCase(); // "HELLO" console.log(str); // "hello"(原字符串未被修改,返回了新字符串) - 引用数据类型:可变(Mutable),我们可以直接修改堆内存中的数据内容(如添加 / 删除对象属性、修改数组元素),无需创建新的对象;
javascript
运行
let arr = [1, 2, 3]; arr.push(4); console.log(arr); // [1,2,3,4](原数组被直接修改)
4. 比较方式不同
- 基础数据类型:值比较,判断两个基础类型变量是否相等,只需比较它们的具体值是否一致(
==会进行类型转换,===严格比较值和类型);javascript
运行
console.log(10 === 10); // true console.log("hello" === "hello"); // true console.log(10 === "10"); // false(类型不同) - 引用数据类型:引用地址比较,判断两个引用类型变量是否相等,比较的是它们是否指向堆内存中的同一个地址,而非数据内容是否一致;
javascript
运行
let obj1 = { name: "张三" }; let obj2 = { name: "张三" }; console.log(obj1 === obj2); // false(两个对象在堆内存中是不同的地址,内容相同但引用不同)
5. 访问速度与内存管理不同
- 基础数据类型:存储在栈内存中,访问速度更快,内存由 JavaScript 引擎自动分配和释放,无需手动管理;
- 引用数据类型:存储在堆内存中,访问时需要先通过栈内存的引用地址找到堆内存中的数据,访问速度相对较慢,内存回收由垃圾回收机制(GC)负责,当一个对象没有任何引用指向它时,垃圾回收机制会在合适时机释放其占用的堆内存。
总结
- JS 数据类型分为基础数据类型(7 种)和引用数据类型(1 种,核心 Object),共计 8 种;
- 基础类型包含 String、Number、Boolean、Undefined、Null、Symbol、BigInt,引用类型包含所有 Object 衍生类型(数组、函数等);
- 类型判断有 4 种核心方式:typeof(基础类型首选)、instanceof(引用类型子类判断)、Object.prototype.toString.call ()(万能精准)、特殊方法(Array.isArray () 等);
- 两类数据的核心差异集中在:存储位置(栈 / 堆)、赋值传参(值传递 / 引用传递)、可变性(不可变 / 可变)、比较方式(值比较 / 地址比较)、内存管理(自动释放 / 垃圾回收)。
掌握这些知识点,能帮助我们在开发中更合理地使用变量、避免数据修改冲突、精准判断数据类型,为编写高效、可靠的 JavaScript 代码打下坚实基础。
