全面解析 ECMAScript 规范中的 7 种原始数据类型(Number、String、Boolean、Undefined、Null、Symbol、BigInt),深入剖析内存机制、不可变性、类型检测方案及常见陷阱。
JavaScript 基本数据类型有哪些?7种原始类型详解
时间:2026-11-02
浏览:119
文章目录
Number、String、Boolean、Undefined、Null、Symbol 和 BigInt。原始类型的值直接存放在调用栈中,具备不可变性(Immutable),掌握它们的底层特征与边界行为是写出健壮代码的基石。
一、什么是原始数据类型?
在 JavaScript 中,“原始类型”(Primitive Type,亦称基本数据类型)是指那些最基础、不可再拆分的值。与对象类型(Object,包括普通对象、数组、函数、日期等)相比,原始数据类型具有以下三个核心本质特征:
- 按值访问(Pass by Value):当一个变量被赋予原始类型值时,变量存储的是该值的实际内容。在赋值或作为函数参数传递时,传递的是这个值的独立副本,修改副本不会对原始变量产生任何副作用。
- 不可变性(Immutability):原始类型的值本身在创建后永远无法被修改。我们可以给变量重新赋予一个新值,但绝不可能在原地更改已有原始值内部的数据。例如字符串的
toUpperCase()或slice()方法并不会修改原字符串,而是创建并返回一个全新的字符串。 - 存放于栈内存(Stack Memory):由于原始类型所占用的内存空间是固定的,JavaScript 引擎会在调用栈(Call Stack)中直接为其分配内存空间,存取速度极快,且由执行上下文自动管理其生命周期。
二、7 种原始数据类型深度剖析
1. Number(数值类型)
JavaScript 中的 Number 类型采用 IEEE 754 标准双精度 64 位浮点数格式表示。无论写入的是整数还是带小数的浮点数,在底层均存储为 64 位浮点数。
由于采用浮点数表示,Number 存在一个安全整数范围:即 Number.MIN_SAFE_INTEGER(-(253 - 1))到 Number.MAX_SAFE_INTEGER(253 - 1,即 9007199254740991)。一旦整数超过该范围,计算就会出现精度丢失。
此外,Number 包含三个特殊的数值:
NaN(Not-a-Number):表示非数字,通常发生于非法数学运算(如0 / 0或parseInt('abc'))。需要特别注意,NaN === NaN的结果为false,判断某个值是否为NaN必须使用Number.isNaN()。Infinity和-Infinity:正无穷与负无穷,当数值超过浮点数上限(例如非零数除以 0:1 / 0)时产生。+0与-0:在绝大多数运算中两者等价,但在除法运算中符号不同:1 / +0 === Infinity,而1 / -0 === -Infinity。可使用Object.is(+0, -0)准确区分。
// 浮点数典型精度问题
console.log(0.1 + 0.2 === 0.3); // 输出: false (实际为 0.30000000000000004)
// 解决方案:使用 Number.EPSILON 容差比较
function areNumbersClose(a, b) {
return Math.abs(a - b) < Number.EPSILON;
}
console.log(areNumbersClose(0.1 + 0.2, 0.3)); // 输出: true
2. String(字符串类型)
String 类型用于表示文本数据,它是由零个或多个 16 位 Unicode 字符组成的字符序列(UTF-16 编码单元)。字符串可以通过单引号(')、双引号(")或 ES6 反引号模板字符串(`)定义。
字符串的关键特质是完全不可变。当对字符串执行拼接、替换、截取时,底层均是分配新内存并生成新字符串:
let title = "javascript";
title[0] = "J"; // 静默失败(严格模式下报错),原始字符串未变
console.log(title); // 输出: "javascript"
// 常见字符串操作均返回新值
let upperTitle = title.toUpperCase();
console.log(upperTitle); // 输出: "JAVASCRIPT"
console.log(title); // 输出: "javascript"(原值不受影响)
3. Boolean(布尔类型)
Boolean 类型仅有两个合法的字面量值:true(真)与 false(假),用于逻辑判断与流程分支控制。
在 JavaScript 条件语句(如 if、三元表达式)中,任意类型的值都会被隐式转换为布尔值。JavaScript 中仅有以下 8 个值会被转换为 false(统称为 Falsy 假值),其余所有值(包括空对象 {}、空数组 []、字符串 "0")均会被转换为 true(Truthy 真值):
false0、-0、0n(BigInt 零)""(空字符串)nullundefinedNaNdocument.all(浏览器宿主特殊历史遗留对象)
4. Undefined(未定义类型)
Undefined 类型只有一个值,即 undefined。它在语义上表示“变量已声明,但尚未被赋予具体值”。
在以下常见场景中,引擎会自动返回 undefined:
- 声明了变量但未赋值:
let score; // undefined - 访问对象上不存在的属性:
const user = {}; console.log(user.age); // undefined - 函数没有通过
return显式返回值,或return后未跟表达式:function fn() {} console.log(fn()); // undefined - 调用函数时未传入对应形参:
function log(msg) { return msg; } log(); // undefined
let a = undefined;,但工程最佳实践极不提倡这样做。如果需要人为清空或显式标记一个变量“无有效值”,应当赋予 null,将 undefined 留给引擎作为未初始化的默认状态。
5. Null(空值类型)
Null 类型同样只有一个值,即 null。它在语义上表示“有意缺省的空对象值”或“空指针引用”。
与 undefined 相比,null 是由开发者主动赋值给变量的,常用于释放对象引用(辅助垃圾回收)或在 DOM API 中表示未找到对应节点(例如 document.getElementById('not-exist') 返回 null)。
typeof null 时,返回结果是 "object"。这是 JavaScript 最初版本实现中的一个著名设计缺陷:早期 JS 引擎用 32 位单元存储变量,低 1~3 位作为类型标记,其中 000 代表对象,而 null 被表示为空指针(全零),因此其类型标记恰好为 0,导致 typeof 误判为 object。TC39 委员会曾提议修复该 bug,但为了保持与现有数以亿计 Web 站点的向前兼容性而被否决。因此判断 null 必须使用严格相等运算符:val === null。
6. Symbol(符号类型,ES6 引入)
Symbol 在 ECMAScript 2015(ES6)中引入,用于生成一个全局独一无二、不可变的值。每个由 Symbol() 函数创建的值都是唯一的,即便传入完全相同的描述字符串也是两个不同的 Symbol。
Symbol 的核心用途包括:
- 防止对象属性名冲突:在给第三方库传入的对象扩展属性或混入(Mixin)方法时,使用 Symbol 作为属性名可确保绝不覆盖已有或未来的同名属性。
- 模拟受保护/私有属性:以 Symbol 作为属性名时,常规的
for...in、Object.keys()或JSON.stringify()均无法直接遍历到该属性(需通过Object.getOwnPropertySymbols()或Reflect.ownKeys()获取)。 - 内置知名符号(Well-Known Symbols):如
Symbol.iterator(自定义迭代器)、Symbol.hasInstance(自定义 instanceof 行为)等,允许开发者直接干预语言底层行为。
const idA = Symbol("userId");
const idB = Symbol("userId");
console.log(idA === idB); // 输出: false (两者完全独立)
const user = {
name: "Bob",
[idA]: 10001
};
console.log(user[idA]); // 输出: 10001
console.log(Object.keys(user)); // 输出: ["name"] (Symbol 属性被隐藏)
7. BigInt(大整数类型,ES2020 引入)
BigInt 在 ECMAScript 2020(ES11)中正式加入,旨在解决 Number 无法精确表示超过 2^53 - 1(9007199254740991)的超大整数的短板。BigInt 可以安全地对任意位数的整数进行精确计算,常见于高精度时间戳、雪花算法分布式 ID、密码学哈希等场景。
创建 BigInt 的方式是在整数末尾附加字符 n,或调用 BigInt() 转换函数:
// 超过 Number 安全上限时,普通 Number 发生精度失真
const unsafeNum = 9007199254740991 + 2;
console.log(unsafeNum); // 结果异常,计算错误
// 使用 BigInt 精确计算
const safeBig = 9007199254740991n + 2n;
console.log(safeBig); // 输出: 9007199254740993n (准确无误)
- 禁止与 Number 隐式混合运算:执行
10n + 5会直接抛出TypeError: Cannot mix BigInt and other types。必须显式转换后才能运算,如10n + BigInt(5)。 - 默认不可被 JSON 序列化:执行
JSON.stringify({ id: 123n })会报错TypeError: Do not know how to serialize a BigInt,向后端传输时建议事先转为 String。
三、如何精准判断 JavaScript 数据类型?
在实际项目开发中,类型判断往往是 Bug 频发的高危地带。仅仅依赖 typeof 操作符是远远不够的,开发者需要根据数据类型选择正确的判定策略。
对于不同类型的检测,应当牢记以下分级判断准则:
- 使用
typeof判定:适用于string、boolean、undefined、symbol、bigint以及function。 - 判定
null:必须严格使用val === null。 - 判定
NaN:必须使用Number.isNaN(val),严禁使用全局函数isNaN(val)(全局函数会在判断前执行隐式类型转换,导致isNaN('hello')返回true)。 - 判定数组:使用原生静态方法
Array.isArray(val)。 - 终极通用判别式:使用
Object.prototype.toString.call(val)。无论传入什么原始类型或复杂内置对象,它都能准确返回标准的内部原型标签:
function getType(val) {
if (val === null) return "null";
if (typeof val !== "object") return typeof val;
return Object.prototype.toString.call(val).slice(8, -1).toLowerCase();
}
console.log(getType("hello")); // "string"
console.log(getType(123n)); // "bigint"
console.log(getType(null)); // "null"
console.log(getType([1, 2])); // "array"
console.log(getType(new Date())); // "date"
四、生产环境避坑指南与最佳实践
理解 7 种原始类型的内部原理最终是为了在工程实践中写出健壮、防御性强的代码。以下总结了 4 条高频开发规范:
- 坚决杜绝宽松相等(
==):在任何情况下优先使用严格全等(===)。==会触发极其晦涩的隐式类型转换链条,如0 == ''为true、false == []为true,给业务逻辑埋下巨大隐患。 - 使用空值合并操作符(
??)替代逻辑或(||):当为变量指定默认值时,||会将0、false、""一并当作假值拦截;而??仅在左侧操作数为null或undefined时生效,完美保留合法的0或false。 - 前后端传递雪花 ID 强制使用字符串:后端生成的 64 位整型分布式 ID(如雪花算法 Snowflake ID,通常为 19 位整数)已远超前端
Number.MAX_SAFE_INTEGER。如果后端以 JSON 纯数值格式返回,前端在解析的一瞬间末几位就会被强制截断变零。务必协调后端在序列化为 JSON 时以 String 输出。 - 类型转换一律采用显式转换:避免使用
+val(转数字)或val + ''(转字符串)或!!val(转布尔)等简写,统一使用标准构造函数Number(val)、String(val)、Boolean(val),代码自解释性更强,易于团队代码审计。
实用工具推荐
绿化、压缩、噪点和虚拟水印,自由调节做旧程度。
选择画质和输出格式,预览压缩前后的大小。