一、JS 的 8 种数据类型:一份"户口本"
根据 ECMA-262 规范(JS 的"宪法"),JS 一共有 8 种数据类型:
1.1 原始数据类型(Primitive)------ 7 种
| 类型 | 说明 | 举例 |
|---|---|---|
number |
数值 | 42, 3.14, NaN |
string |
字符串 | 'hello', "world" |
boolean |
布尔值 | true, false |
null |
空值 | null |
undefined |
未定义 | undefined |
symbol |
唯一标识符(ES6 新增) | Symbol('foo') |
bigint |
大整数(ES6 新增) | 123n |
1.2 复杂数据类型 ------ 1 种
| 类型 | 说明 | 举例 |
|---|---|---|
object |
对象(包括数组、函数等) | {}, [], function(){} |
ES6 以前只有 6 种 (number、string、boolean、null、undefined、object),后来加了 symbol 和 bigint,变成了 8 种。
这就像一个村子原来只有 6 户人家,后来搬来了 2 户新邻居,村长重新统计了一下人口,宣布"我们村现在有 8 户了"。
二、null 和 undefined:JS 界的"双胞胎"谜案
2.1 undefined:我还没准备好
老师总结了 undefined 出现的四种场景:
javascript
let a; // 未初始化
console.log(a); // undefined
let obj = {}; // 不存在的属性
console.log(obj.name); // undefined
function noReturn() {}
console.log(noReturn()); // 没有返回值的函数
let arr = [1, 2, 3];
console.log(arr[5]); // 访问不存在的数组索引
undefined 的含义是:这个变量/属性/位置"还没被赋值"。 就像一个快递柜,上面写着你的名字,但里面是空的------不是你故意不放东西,是快递还没到。
2.2 null:我故意留空的
再来看 null:
javascript
let obj = {
name: 'Alice',
address: null // 故意设置为空
}
console.log(obj.address); // null
console.log(obj.age); // undefined
null 的含义是:这里"应该有个值,但我故意设为空"。 就像你订了一个快递柜,快递到了,但你把东西取出来了,柜子空了------这是你"故意"的行为。
老师还提到一个实际用途------手动释放内存:
javascript
// 假设有一个超大对象
// let largeObject = {
// data: new Array(100000000).fill("qxh")
// }
// 用完了,手动回收内存
// largeObject = null;
把变量设为 null,就断开了对对象的引用,垃圾回收器就可以把那块内存回收了。这就像你退租了房子,房东就可以把房子租给别人了。
2.3 一张表分清 null 和 undefined
| 对比项 | null |
undefined |
|---|---|---|
| 含义 | 故意设置为空 | 还没被赋值 |
| 场景 | 主动清空变量、释放内存 | 未初始化、不存在的属性、无返回值 |
| 态度 | "我知道这里需要值,但我选择留空" | "我不知道这里需要什么值" |
| 比喻 | 快递取走了,柜子空了 | 快递还没到,柜子等着呢 |
三、内存分配:栈和堆,JS 的"双仓库"系统
3.1 冯诺依曼架构:一切从内存开始
老师从计算机底层讲起:
冯诺依曼架构------运算器、存储器、输入、输出。
代码存在硬盘(外存)里,编译后调入内存执行。执行上下文(变量环境、词法环境)被推入调用栈,就在栈内存中。
3.2 栈内存 vs 堆内存
| 特性 | 栈内存(Stack) | 堆内存(Heap) |
|---|---|---|
| 速度 | 快 | 相对慢 |
| 空间 | 小 | 大 |
| 存储内容 | 原始数据类型的值 + 对象的地址 | 引用数据类型的实际数据 |
| 管理方式 | 函数执行完自动出栈 | 垃圾回收器管理 |
老师解释:编译阶段给函数执行上下文分配的栈空间是精确计算的。函数执行完出栈后,栈顶指针偏移一下就切换到下一个上下文。快速、稳定、可扩展。
3.3 拷贝式赋值 vs 引用式赋值
这是今天最重要的知识点之一。看代码:
javascript
// 原始数据类型:拷贝式赋值(复印机模式)
let a = null;
let b = a; // 拷贝!b 是 a 的复印件
b = 2;
console.log(a, b); // null, 2 ------ a 不受影响!
// 引用数据类型:引用式赋值(遥控器模式)
let obj1 = {name: "xll"}
let obj2 = obj1; // 引用!obj2 和 obj1 指向同一个对象
obj2.company = "TikTok";
console.log(obj1, obj2); // 两个都多了 company 属性!
原始数据类型是"复印机模式"------你复印一份文件,在复印件上涂涂画画,原件不受影响。
引用数据类型是"遥控器模式"------你和朋友各拿一个遥控器,但控制的是同一台电视。你换了频道,朋友看到的也变了。
这就是为什么修改 obj2.company 会影响 obj1------因为它们指向堆内存中的同一个对象。
四、Number:JS 的数学水平,连小学生都不如
4.1 经典名场面:0.1 + 0.2 != 0.3
javascript
let a = 0.1;
let b = 0.2;
console.log(a + b); // 0.30000000000000004
没错,JS 算出来的 0.1 + 0.2 不是 0.3,而是一串带尾巴的数字。
老师解释:JS 统一使用二进制来存数值 ,而 0.1 和 0.2 在二进制中是无限循环小数(就像十进制里的 1/3 = 0.3333...),存不精确,算出来自然也不精确。
这就像你用"四舍五入"记了一笔账,记了无数次之后,误差累积,最后对不上账了。不是你算错了,是存储方式决定了精度上限。
所以记住:JS 不擅长精确计算。 涉及钱的场景,千万别直接用浮点数加减。
4.2 BigInt:当 Number 不够用的时候
javascript
let num1 = 999999999999999999999999999999999999999999999999999999999999999n
let num2 = 123456789098765433467324577654789008733233456899003466788924243n
console.log(num1 + num2, typeof num1); // bigint
console.log(num1 + 1); // 报错!bigint 不能直接和 number 运算
BigInt 是 ES6 新增的数据类型,专门处理超大整数 。用法很简单------在数字后面加个 n。
但有个坑:BigInt 和 Number 不能直接混用运算 。num1 + 1 会报错,必须写成 num1 + 1n。
这就像你开着一辆卡车(BigInt),想和一辆自行车(Number)赛跑,裁判说"你们不是一个量级的,没法比"。
五、Symbol:JS 里的"身份证号"
5.1 每一个 Symbol 都是独一无二的
javascript
console.log(Symbol("zzh") === Symbol("zzh")); // false!
console.log(typeof Symbol("zzh")); // symbol
看到没?即使传入相同的参数 "zzh",两个 Symbol 也不相等。
这就像两个人都叫"张三",但身份证号不同------名字可以重复,但身份证号是唯一的。Symbol 就是 JS 世界里的身份证号。
5.2 Symbol 可以做对象的"隐藏属性"
javascript
let obj = {
[Symbol()]: "value",
prop: "2"
}
用 Symbol 作为对象的属性名,这个属性就"藏"起来了------普通的 for...in、Object.keys() 都遍历不到它。
这就像你在家里藏了一个保险箱,外人翻箱倒柜也找不到------只有拿着对应 Symbol 钥匙的人才能打开。
六、总结:8 种数据类型,每个都有自己的"脾气"
| 数据类型 | 分类 | 脾气 | 注意点 |
|---|---|---|---|
number |
原始 | 算数不靠谱 | 0.1 + 0.2 不等于 0.3 |
string |
原始 | 老实人 | 没什么坑 |
boolean |
原始 | 非黑即白 | 只有 true 和 false |
null |
原始 | 故意留空 | 和 undefined 别搞混 |
undefined |
原始 | 还没准备好 | 四种出现场景要记住 |
symbol |
原始(ES6) | 独一无二 | 可做隐藏属性 |
bigint |
原始(ES6) | 超大整数 | 不能和 number 混算 |
object |
引用 | 共享地址 | 赋值是"遥控器模式" |
理解数据类型,就是理解 JS 的地基。 你不知道地基是什么材料,房子盖得再漂亮,也可能随时塌掉。
写在最后
今天最大的收获,是理解了"栈"和"堆"的区别。以前只知道"引用类型赋值会影响原对象",但不知道为什么。现在知道了:原始类型存值,引用类型存地址。
下次面试官问你:"null 和 undefined 有什么区别?"
你可以淡定地说:
"
undefined是变量声明了但还没赋值,或者访问了不存在的属性;null是有意设置的一个空值,表示'这里应该有东西,但我选择留空'。null还可以用来手动释放内存,断开对象引用,帮助垃圾回收。"
然后看着面试官频频点头的样子,心里默念:这波,又稳了。
本文所有代码示例均来自课堂学习资料,真实可运行。