简介:本文系统梳理 JavaScript 的 8 种数据类型,包括 7 种原始类型与 1 种引用类型,并结合堆栈存储机制说明它们的底层差异。读者可掌握各类型的核心特征、使用场景与常见误判陷阱,为类型判断、内存优化和面试准备建立清晰知识框架。
JavaScript 是一种动态类型语言,变量在声明时无需指定类型,赋值时由引擎自动推断。这并不意味着 JavaScript 没有类型体系——相反,ECMAScript 规范明确定义了语言层面的数据类型集合。理解这套类型体系,是掌握 JavaScript 一切行为特性的基础。
从数据存储与传递方式的角度,JavaScript 的全部值可以划分为两大类别:
| 类别 | 包含类型 | 存储方式 | 传递方式 |
|---|---|---|---|
| 原始类型(Primitive Types) | Undefined、Null、Boolean、Number、String、Symbol、BigInt | 栈内存(Stack) | 按值传递 |
| 引用类型(Reference Types) | Object(含 Function、Array、Date、RegExp 等) | 堆内存(Heap) | 按引用传递 |
总计 7 种原始类型 + 1 种引用类型,也就是常说的“8 种数据类型”。下面依次展开说明。
原始类型的值是不可变的,一旦创建就无法修改。对原始类型变量的“修改”,本质上是重新赋值为一个新的原始值,而非在原有值上做变更。每个原始类型的值都是独立存储、直接包含在变量访问位置中的原子单位。
Undefined 类型只有一个值:undefined。它表示“变量已声明但尚未赋值”的状态。
let a;console.log(a); // undefinedconsole.log(typeof a); // "undefined"
需要区分的是:未声明变量直接访问会抛出 ReferenceError,而已声明未赋值的变量则返回 undefined。此外,函数没有显式 return 时,默认返回 undefined。
Null 类型同样只有一个值:null。它表示“空值”或“对象不存在”,通常由开发者主动赋予,表达一种“有意的空”的语义。
let obj = null; // 明确表示 obj 当前不指向任何对象console.log(typeof null); // "object"
typeof null 返回 "object" 是 JavaScript 语言历史遗留问题:早期实现中,类型标签与对象使用同一标记位,导致 null 被误判为对象。这一行为已被规范固化,无法修复,因此在实际类型判断时不能仅依赖 typeof 处理 null。
Boolean 类型只有两个值:true 和 false。在逻辑判断中,任何类型的值都会被隐式转换为布尔值参与条件运算。JavaScript 中仅有以下值在布尔上下文中会被转换为 false,其余全部为 true:
false0、-0、0n""(空字符串)nullundefinedNaN这组值常被称为 Falsy 值,是理解条件判断与逻辑运算符短路行为的核心。
Number 类型基于 IEEE 754 双精度 64 位浮点数标准表示,这意味着:
1 和 1.0 在底层完全相同。Number.MAX_SAFE_INTEGER(2⁵³ - 1),超过该范围可能出现精度丢失。Infinity、-Infinity 和 NaN(Not a Number)都属于 Number 类型。
console.log(0.1 + 0.2); // 0.30000000000000004console.log(Number.MAX_SAFE_INTEGER); // 9007199254740991console.log(typeof NaN); // "number"
浮点数精度问题源于二进制无法精确表示某些十进制小数,这在涉及金额计算、比较运算时需特别注意,通常可借助整数运算或任意精度方案规避。
String 类型用于表示文本数据,底层为 不可变的 UTF-16 编码字符序列。字符串一旦创建,其内容不可修改;对字符串的拼接、截取等操作返回的是新字符串。
let s = "hello";s[0] = "H"; // 非严格模式下静默失败console.log(s); // "hello"——原字符串未被改变let t = s.toUpperCase();console.log(t); // "HELLO"——新字符串
字符串支持索引访问、模板字面量、常用方法等能力,但需牢记其不可变特性对性能的潜在影响——频繁拼接大字符串时,每次操作都在生成新对象。
Symbol 是 ES6 引入的原始类型,用于创建全局唯一的标识符。即使两个 Symbol 的描述完全相同,它们本身也互不相等。
const s1 = Symbol("id");const s2 = Symbol("id");console.log(s1 === s2); // falseconsole.log(typeof s1); // "symbol"
Symbol 的主要应用场景包括:
Symbol.iterator、Symbol.toStringTag 等内置符号定制对象行为;Object.getOwnPropertySymbols 仍可发现,但避免了常规枚举)。BigInt 是 ES10 引入的原始类型,用于表示任意精度整数,解决了 Number 类型在大整数场景下的精度丢失问题。
const big = 9007199254740993n; // 字面量末尾加 nconst another = BigInt("9007199254740993");console.log(big); // 9007199254740993nconsole.log(typeof big); // "bigint"console.log(9007199254740993); // 9007199254740992 —— Number 精度丢失
使用 BigInt 时需注意:
Math 对象中的大部分方法;Object 是 JavaScript 中唯一的引用数据类型,其本质是一组无序键值对的集合。键为字符串或 Symbol,值可以是任意类型,包括其他对象,从而构成任意深度的嵌套结构。
const person = {name: "Alice",age: 28,hobbies: ["reading", "coding"],address: {city: "Beijing",street: "X Road"}};
Function、Array、Date、RegExp 等并非独立于 Object 的数据类型,它们本质上都是 Object 的特殊形式或被增强的子类型:
typeof 特殊返回 "function";length 属性的对象,Array.isArray() 可精确判别;这也解释了为什么 typeof [] 返回 "object"、typeof new Date() 返回 "object"——它们本质都是对象。
JavaScript 不提供用户自定义底层数据类型机制(如 C 语言中的结构体),开发者只能在 Object 基础之上构建应用层的数据模型。所有值最终都归属于前述 8 种类型之一。
理解原始类型与引用类型在内存层面的差异,比简单记忆类型列表更加重要。这直接解释了许多令人困惑的行为。
原始类型赋值时,会复制一份值给新变量,两者互不影响:
let x = 10;let y = x;y = 20;console.log(x); // 10 —— x 不受 y 修改影响
引用类型赋值时,复制的是引用地址,两个变量指向同一个堆内存对象:
const objA = { value: 10 };const objB = objA;objB.value = 20;console.log(objA.value); // 20 —— objA 与 objB 指向同一对象
函数传参同样遵循此规则:原始类型实参按值传递,函数内部修改不会外溢;引用类型实参传递的是引用地址,函数内部对对象属性的修改会影响外部对象。
=== 对于原始类型比较值是否相同;=== 对于引用类型比较引用地址是否相同,即使两个对象内容完全一致,只要不是同一引用,比较结果仍为 false。
const a = { x: 1 };const b = { x: 1 };console.log(a === b); // false —— 不同引用地址console.log(a === a); // true —— 同一引用
准确判断数据类型是日常开发中的高频需求,常用手段各有优劣:
优点:简洁直观,适合快速判断原始类型。
局限:typeof null 返回 "object";无法区分 Array、Date、正则等具体对象类别。
typeof 42; // "number"typeof "str"; // "string"typeof Symbol(); // "symbol"typeof 10n; // "bigint"typeof undefined; // "undefined"typeof null; // "object" —— 历史陷阱typeof {}; // "object"typeof []; // "object" —— 无法区别数组typeof function(){};// "function"
这是当前最为通用的精确判断方案,能够返回形如 "[object Type]" 的标准字符串:
const typeOf = (val) =>Object.prototype.toString.call(val).slice(8, -1).toLowerCase();typeOf(null); // "null"typeOf([]); // "array"typeOf(new Date()); // "date"typeOf(/regex/); // "regexp"
instanceof 用于检测对象原型链中是否存在指定构造函数的原型,但跨执行环境(如 iframe)时可能失效。Array.isArray() 是判断数组的推荐方式,不受跨环境限制。
JavaScript 的 8 种数据类型是语言特性的根基。7 种原始类型以不可变、按值存储、按值传递为特征,涵盖日常开发中的标量场景;引用类型以对象为核心,支持复杂数据结构的构建,按引用地址传递。两者在内存存储、复制行为、相等比较等方面的差异,是理解 JavaScript 运行机制的关键。准确判断类型不仅需要熟悉 typeof 的适用边界,也需掌握 toString 方案与针对性 API 的组合使用。只有建立完整的类型认知,才能在开发中做出更可靠的设计决策。