JS 数据类型 + 内存分配:从 8 种类型到栈堆模型

JS 一共有 8 种数据类型(ECMA262 规范)。搞懂"值存在哪里、怎么传",是理解所有 JS 行为的基础。


一、8 种数据类型总览

javascript 复制代码
8 种数据类型
├── 原始数据类型(Primitive,共 7 种)
│   ├── Number        数值(整数、小数、Infinity、NaN)
│   ├── BigInt        大数(ES2020 新增,处理超大整数)
│   ├── Boolean       布尔(true / false)
│   ├── String        字符串
│   ├── Symbol        唯一标识符(ES6 新增)
│   ├── null          空的对象引用(有意为空)
│   └── undefined     未初始化 / 不存在
│
└── 引用数据类型(Reference,共 1 种,涵盖所有对象)
    └── Object        对象(含 Array、Function、Date、RegExp 等)

规范另一种说法:原始数据类型 6 种 = null、undefined、numeric(再分 Number + BigInt)、Boolean、String、Symbol,加 1 种 Object,合计 8 种。

ES6 以前只有 6 种:Number、Boolean、String、null、undefined、Object。ES6 加 Symbol,ES2020 加大 BigInt,凑齐 8 种。


二、原始数据类型(简单类型)

1. Number(数值)

JS 所有数值统一用二进制浮点存,所以有个经典坑:

来自 3.js

js 复制代码
let a = 0.1;
let b = 0.2;
console.log(a + b);  // 0.30000000000000004  不是 0.3!

原因:十进制小数转二进制会无限循环,只能截断保存,产生精度误差。比如 1/3 在十进制里是 0.333...,JS 用 64 位存,会有微小误差。

Number 包含的特殊值

  • Infinity:正无穷大
  • -Infinity:负无穷大
  • NaN:Not a Number(比如 'a' - 1 得到 NaN)

2. BigInt(大数,ES2020 新增)

当 Number 存不下超大整数(超过 2^53-1 就会精度丢失),数据末尾加上n 就变成 BigInt ,可以精确存任意大的整数。

用 BigInt:

js 复制代码
let num1 = 999999999999999999999999999999999999999999999999999999999999999n
let num2 = 123456789098765433467324577654789008733233456899003466788924243n
console.log(num1 + num2);      // 能精确算出
console.log(typeof num1);      // 'bigint'
console.log(num1 + 1n);        // 加的数也要带 n 后缀

注意 :BigInt 字面量结尾要加 n,BigInt 不能和 Number 直接运算(类型不同)。

3. Boolean(布尔值)

只有两个值:truefalse,用于条件判断。

4. String(字符串)

JS 字符串是不可变的,拼接/修改会创建新字符串,不会改原字符串。

5. Symbol(符号,ES6 新增)

唯一的标识符,用函数创建,本身是简单数据类型:

js 复制代码
console.log(Symbol('张三') === Symbol('张三')); // false  ← 即使描述一样,也是不同的
console.log(typeof Symbol('张三'));               // 'symbol'
console.log(Symbol());                             // 不传描述也行,绝对唯一

用途:作为对象的独一无二的属性 key,避免和其他属性名冲突:

js 复制代码
let obj = {
    [Symbol()]: 'value',   // Symbol 作属性名,不会和普通属性重名
    prop: '2'
};

口诀:Symbol = "传什么描述都是假的,创建出来就是独一份"。

6. null vs undefined(最易混的一对)

两者都表示"空/没有",但语义完全不同。

null undefined
语义 有意设置的空引用(应该有值,但现在没有) 不应该有值的空(未初始化/不存在)
出现场景 ① 手动清空对象 obj = null ② 表示空的地址/联系/关系 ① 声明变量未赋值(var/let) ② 对象属性不存在 ③ 函数没有 return ④ 访问不存在的数组索引
来自 1.js obj.address = null(地址字段有意设为空) obj.age(age 属性不存在)
typeof typeof null → 'object'(历史 Bug) typeof undefined → 'undefined'

实战代码对比1.js):

js 复制代码
let obj = {
    name: "Alice",
    address: null     // ← 有意设为空:应该有地址这个字段,只是现在没填
};
console.log(obj.address);   // null        ← 字段存在,值是空的
console.log(obj.age);       // undefined   ← 连 age 这个字段都不存在

undefined 的四种场景

js 复制代码
// ① 声明变量但未赋值
let a;
console.log(a);           // undefined

// ② 对象属性不存在
let obj = {};
console.log(obj.property);   // undefined

// ③ 函数没有返回值
function noReturn() { }
console.log(noReturn());     // undefined

// ④ 访问不存在的数组索引
let arr = [1, 2, 3];
console.log(arr[5]);         // undefined

null 的实际用途:清空对象释放内存1.js):

js 复制代码
let largeObject = {
    data: new Array(10000000).fill("hgh")
};
// 手动回收内存(告诉 GC 这个对象可以被回收)
largeObject = null;

typeof null === 'object' 的 Bug

这是 JS 最著名的历史 bug。null 明明是原始类型,但 typeof null 返回 'object'。原因是早期 JS 用低 3 位存类型标记,对象标记是 000,null 表示全 0,所以被误判成对象。因为修复会导致太多老代码崩溃,至今保留。

一句话区分

null = "特意留空" (应该有,但是现在没有);undefined = "根本没有"(没初始化/不存在)。


三、引用数据类型(复杂类型)

1. 所有对象都是 Object 系

javascript 复制代码
Object
├── 普通对象 {name: 'xhj'}
├── 数组 [1, 2, 3]
├── 函数 function() {}
├── 日期 new Date()
├── 正则 /abc/
├── Map / Set
└── ... (所有你能用 new 造出来的)

虽然它们长得不一样,但在内存存储机制上都属于引用类型,行为一致。

2. 原始 vs 引用:赋值的区别(核心!)

js 复制代码
// 原始数据类型:拷贝式赋值(复印机)
let a = null;
let b = a;       // 复制值,a 和 b 互不影响
b = 2;           // 改 b
console.log(a);  // null  ← a 不变

// 引用数据类型:引用式赋值(钥匙)
let obj1 = { name: "xhj" };
let obj2 = obj1;            // 复制地址,obj1 和 obj2 指向同一个对象
obj2.company = "字节跳动";   // 通过 obj2 改
console.log(obj1, obj2);    // 两者都有 company 属性!指向同一个对象

类比

  • 原始类型赋值 = 把文件复印一份,你改复印件,原件不变
  • 引用类型赋值 = 给同一套房再配一把钥匙,用新钥匙进屋装修,用老钥匙打开一看也变了

四、内存分配:栈 vs 堆

1. 从冯诺依曼体系说起

现代计算机都遵循冯诺依曼架构:

复制代码
┌──────────────────────────────────────────────┐
│ 运算器 + 控制器 = CPU(计算核心)              │
│ 存储器:内存(快、小) + 外存/硬盘(慢、大)     │
│ 输入设备:键盘、鼠标...                         │
│ 输出设备:屏幕、打印机...                       │
└──────────────────────────────────────────────┘

代码执行流程

  1. 代码文件存在硬盘(外存)
  2. 编译时调入内存
  3. JS 在内存里进一步划分为

2. 栈内存(Stack)

特点

  • 快(直接 CPU 寻址,比堆快得多)
  • 空间小(几 MB 级别)
  • 先进后出管理

存什么

  • 原始数据类型 :直接存本身 → 赋值时直接拷贝值(复印机)
  • 引用数据类型的引用 :存堆内存的地址 → 赋值时拷贝地址(钥匙),对象本身还在堆里
  • 函数执行上下文(变量环境 + 词法环境)

为什么栈这么快

函数执行上下文对象占用多少空间在编译阶段就能算出来 ,推入调用栈时一次性分配合适的空间。当函数执行完出栈时,只需要移动一下栈顶指针(偏移量),就能立刻切换上下文。不需要 GC、不需要复杂的内存管理。

3. 堆内存(Heap)

特点

  • 相对慢一点(动态分配)
  • 空间大(几百 MB 到几 GB,按需申请)
  • 由 GC(垃圾回收器)管理回收

存什么

  • 引用数据类型本体(对象、数组、函数...的所有属性值)
  • 大小不确定的、动态变化的数据

4. 画图理解

js 复制代码
let a = 123;                    // 原始类型
let obj = { name: 'xhj' };      // 引用类型
css 复制代码
栈内存(Stack)                      堆内存(Heap)
┌─────────────────────┐            ┌──────────────────────────┐
│ 变量环境 / 词法环境   │            │                          │
│                     │            │                          │
│  a: 123             │  ← 值直接存 │      0x888:              │
│  obj: 0x888 ──────────────────────--------->{ name: 'xhj' }       │
│         ↑                        │    (真实对象放在这里)    │
│    存的是地址(引用) |            │                          │
└─────────────────────┘            └──────────────────────────┘

5. 拷贝赋值 vs 引用赋值的内存视角

js 复制代码
let obj1 = { name: "xhj" };
let obj2 = obj1;   // 引用式赋值:栈里复制一份地址,指向堆里同一个对象
obj2.company = "字节跳动";
yaml 复制代码
栈内存                                 堆内存
┌───────────────┐                     ┌────────────────────────────────┐
│ obj1: 0xABC ───┼────────────────────▶│ 0xABC: { name: 'xhj',         │
│ obj2: 0xABC ───┼────────────────────▶│         company: '字节跳动' }   │
└───────────────┘                     └────────────────────────────────┘
     两把钥匙                                        同一套房子

这就是为什么改 obj2 会影响 obj1------两把钥匙开同一套房子。

6. 清空引用的正确方式

js 复制代码
let largeObject = { data: bigArray };
largeObject = null;

内存视角 :把栈里的地址改成 null(断开引用),堆里的对象不再被任何栈引用,GC 就会在合适时机把它回收掉,释放内存


五、完整类型检测

类型 写法 结果 注意
Number typeof 123 'number' ---
BigInt typeof 123n 'bigint' 带 n
Boolean typeof true 'boolean' ---
String typeof 'hi' 'string' ---
Symbol typeof Symbol() 'symbol' ---
undefined typeof undefined 'undefined' ---
null typeof null 'object' ❌ 历史 Bug,不能用 typeof 判 null
Object typeof {a:1} 'object' ---
Array typeof [1,2,3] 'object' ❌ 要判数组用 Array.isArray(arr)
Function typeof function(){} 'function' ✅ 唯一能区分函数的 typeof

判 null 正确方法

js 复制代码
x === null   // ✅ 直接三等比较,typeof null 不可靠

六、总结

1. 8 种数据类型速记

javascript 复制代码
原始 7 种(栈里存值):
  Number BigInt Boolean String Symbol null undefined
引用 1 种(栈里存地址,堆里存本体):
  Object(数组、函数、Date、正则等都是 Object 系)
合计 8 种

2. null vs undefined

null = 特意留空 (应该有值,现在没填);undefined = 根本没有(未初始化/不存在)。

3. 栈 vs 堆

栈内存 堆内存
速度 较慢
空间 小(几 MB) 大(几百 MB+)
存什么 原始值 + 引用地址 + 执行上下文 对象本体
管理方式 出栈即释放(指针偏移) GC 垃圾回收
赋值行为 拷贝式(复印机) 引用式(配钥匙)

4. 核心心法

  1. 8 种类型,7 原始 + 1 引用
  2. typeof null === 'object' 是历史 Bug ,判 null 直接 === null
  3. 原始赋值 = 复印机 ,互不影响;引用赋值 = 配钥匙,一个改全改
  4. 栈存值/地址,堆存对象本体
  5. var a; / obj.不存在 / 函数无 return / 数组越界 → undefined
  6. 设空字段 = null,手动释放内存 = 置 null

一句话原始类型栈里存值,引用类型栈里存地址、堆里放对象,这就是所有差异的根源。

相关推荐
xiaominlaopodaren1 小时前
three.js地图数学基础(三):实战墨卡托
javascript·gis·three.js
雪碧聊技术1 小时前
力扣 回溯法 | LCR 020. 回文子串
javascript·算法·leetcode
wabs6661 小时前
关于哈希表【力扣454.四数相加II的思考】
数据结构·算法·leetcode·散列表
白狐_7982 小时前
408数据结构第7章:查找①——顺序查找、折半查找、分块查找知识总结
数据结构
敲敲敲敲暴你脑袋2 小时前
升级啦!纯前端打包下载离线地图
javascript·gis·canvas
SendTomo2 小时前
send.wang私传网:P2P直连传大文件首选工具
javascript·网络·网络协议·webrtc·p2p
晓得迷路了2 小时前
栗子前端技术周刊第 141 期 - Next.js 16.3、npm 安全事件、2026 CSS 现状调查报告结果...
前端·javascript·npm
ShineWinsu2 小时前
对于C++中unordered_map的详细介绍
数据结构·c++·算法·面试·stl·哈希表·unordered_map
lingran__3 小时前
C++ 高阶数据结构:AVL 树万字详解|原理、四种旋转、key‑value 实现、调试校验、set/map/红黑树 底层前置知识
数据结构·c++·二叉搜索树·平衡二叉树·avl树·红黑树前置