前言
在 JavaScript 的世界里,数据类型并非简单的分类,而是一场由调用栈 与堆内存共同演绎的精妙舞剧。每一种数据类型都有其专属的 "舞台位置",而 v8 引擎则是这场舞剧的幕后导演。让我们以色彩为笔,代码为证,拆解这场舞剧的每一个角色与舞台设计。
一、原始类型(简单类型):调用栈上的 "轻骑兵"
原始类型是 JavaScript 里的 "轻量级选手",它们的值直接存储在调用栈中,访问速度极快。我们可以把调用栈想象成一个 "快速储物柜",小而精的物品(原始值)就直接放在这里,无需额外寻址。
1. string(字符串)------ 彩色丝带般的文本载体
字符串是字符的有序序列,用单引号、双引号或模板字符串包裹,像一条串联意义的彩色丝带。
例如:
ini
let myname = 'Henry';
let greeting = "Hello, JavaScript!";
// 模板字符串(支持变量嵌入与换行)
let userInfo = `Name: ${myname}, Age: 28`;
console.log(userInfo); // 输出:Name: Henry, Age: 18

2. number(数字)------ 量化世界的基石
包含整数、浮点数,甚至特殊值NaN(非数字)、Infinity(无穷大),是描述数量的核心类型。
看个例子:
ini
let age = 28; // 整数
let height = 1.75; // 浮点数
let total = age + height; // 数字运算:29.75
let notANumber = 'abc' * 2; // 非数字运算,结果为 NaN
console.log(notANumber); // 输出:NaN

3. boolean(布尔)------ 逻辑判断的 "红绿灯"
仅有true(真)和false(假)两个值,是控制代码逻辑流向的关键,像红绿灯一样决定程序的 "通行" 与 "停止"。
javascript
let flag = true
let unFlag = false
if (1){
console.log('真');
} else{
console.log('假');
}

4. undefined------ 未赋值的 "空白标签"
变量声明后未赋值时的默认状态,代表 "值不存在",是 JavaScript 自动分配的 "空白标签"。
ini
let unassignedVar; // 仅声明未赋值
console.log(unassignedVar); // 输出:undefined
let num = 10;
num = undefined; // 手动赋值为 undefined
console.log(num); // 输出:undefined

5. null------ 主动声明的 "空盒子"
与undefined不同,null是程序员主动赋值的 "空值",代表 "刻意为空",像一个被清空后特意标记的空盒子。
ini
let emptyObj = null; // 主动声明空对象
let user = { name: 'Henry' };
user = null; // 清空对象引用
console.log(user); // 输出:null

6. symbol------ 独一无二的 "指纹"
通过Symbol()创建,即使描述相同,也永远不会相等,专为对象属性的唯一性设计。
ini
let id1 = Symbol('userID');
let id2 = Symbol('userID');
console.log(id1 === id2); // 输出:false(描述相同但本质不同)

7. bigInt------ 超大整数的 "专属容器"
解决普通number无法精确表示超大整数(最大值为2的53次方,即2 ** 53)的问题,通过数字后加n或BigInt()创建。
ini
let bigNum1 = 9007199254740993;
console.log(bigNum1); // 输出:9007199254740992(精度丢失)
let bigNum2 = 9007199254740993n;
let bigNum3 = BigInt('9007199254740993');
console.log(bigNum2 === bigNum3); // 输出:true(精确匹配)

二、引用类型:堆内存里的 "豪华庄园"
引用类型是 JavaScript 里的 "重量级选手",它们的值(复杂数据)存储在堆内存中,而调用栈里只保存一个 "指向堆的引用地址"(类似庄园的门牌号)。访问时需先通过栈中的 "门牌号" 找到堆中的实际数据。
1. 数组(Array)------ 线性存储的 "百宝箱"
按下标顺序存储元素,可容纳任意类型数据,通过下标或数组方法操作元素,像一个有序的百宝箱。
sql
let arr = [1, 'a', true, undefined, null]
console.log(arr[1]); // 下标1对应的值是字符串'a',控制台输出'a'
arr.push(123n) // 向数组末尾添加bigInt类型元素123n,添加后数组为[1, 'a', true, undefined, null, 123n]
arr.pop() // 删除数组末尾的最后一个元素(即刚添加的123n),数组恢复为[1, 'a', true, undefined, null]
arr.unshift(Symbol(100)) // 向数组开头添加symbol类型元素,添加后数组为[Symbol(100), 1, 'a', true, undefined, null]
arr.shift() // 删除数组开头的第一个元素(即刚添加的Symbol(100)),数组恢复为[1, 'a', true, undefined, null]
arr.splice(2,1) // 从下标2开始,删除1个元素,执行后数组变为[1, 'a', undefined, null]
arr.splice(4, 0, 123n) // 向数组末尾添加123n,最终数组为[1, 'a', undefined, null, 123n]
console.log(arr); // 输出[1, 'a', undefined, null, 123n]
答案和我们分析的一致: 
2. 对象(Object)------ 键值对组成的 "精密仪器"
以key-value(键值对)形式存储数据,key为字符串或Symbol,value可任意类型,支持嵌套,像一台结构精密的仪器。
例如:
javascript
let obj = {
name: 'Henry',
age: 19,
girlFriend: '无'
}
obj.age = 20 // 将年龄改为20
delete obj.girlFriend // 删除girlfriend键值对
console.log(obj);

3. 函数(Function)------ 可执行的 "魔法配方"
函数是一段可重复执行的代码块,本质是 "可调用的对象",调用时会触发预设逻辑,像一份能生成结果的魔法配方。
依旧上代码:
less
var a = 2
function add() {
var b = 10
return a + b
}
console.log(add());

三、v8 引擎的 "存储哲学":栈与堆的分工艺术
在 Chrome 的 v8 引擎眼中,内存被划分为调用栈 和堆内存,二者各司其职,共同保证 JavaScript 的高效运行:
| 存储区域 | 存储内容 | 特点 | 对应数据类型 |
|---|---|---|---|
| 调用栈 | 原始类型的值、引用地址 | 空间小、访问速度极快 | 7 种原始类型 |
| 堆内存 | 引用类型的实际数据 | 空间大、可存储复杂结构 | 数组、对象、函数等 |
用函数的例子来解析:

这段代码清晰展现了栈与堆的分工。
结语:舞剧的核心逻辑
这场由栈与堆共同演绎的数据存储舞剧,让 JavaScript 既有 "轻骑兵"(原始类型)的迅捷响应,又有 "豪华庄园"(引用类型)的包容能力。通过代码示例,我们能直观看到不同数据类型的定义、操作方式,以及背后的存储逻辑 ------ 原始类型 "值在栈中",引用类型 "址在栈中,值在堆中"。
理解这份分工,就掌握了 JavaScript 内存管理的核心密码,在编写代码时,便能更清晰地预判变量的行为,避免因内存认知偏差导致的 bug,让代码既高效又稳健~
感谢阅读,我会一直努力创造出高质量的文章!