🃏 JS 只有 8 种数据类型,但我花了 2 天才搞懂 null 和 undefined 的区别

一、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),后来加了 symbolbigint,变成了 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.10.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

但有个坑:BigIntNumber 不能直接混用运算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...inObject.keys() 都遍历不到它。

这就像你在家里藏了一个保险箱,外人翻箱倒柜也找不到------只有拿着对应 Symbol 钥匙的人才能打开。


六、总结:8 种数据类型,每个都有自己的"脾气"

数据类型 分类 脾气 注意点
number 原始 算数不靠谱 0.1 + 0.2 不等于 0.3
string 原始 老实人 没什么坑
boolean 原始 非黑即白 只有 truefalse
null 原始 故意留空 undefined 别搞混
undefined 原始 还没准备好 四种出现场景要记住
symbol 原始(ES6) 独一无二 可做隐藏属性
bigint 原始(ES6) 超大整数 不能和 number 混算
object 引用 共享地址 赋值是"遥控器模式"

理解数据类型,就是理解 JS 的地基。 你不知道地基是什么材料,房子盖得再漂亮,也可能随时塌掉。


写在最后

今天最大的收获,是理解了"栈"和"堆"的区别。以前只知道"引用类型赋值会影响原对象",但不知道为什么。现在知道了:原始类型存值,引用类型存地址

下次面试官问你:"nullundefined 有什么区别?"

你可以淡定地说:

"undefined 是变量声明了但还没赋值,或者访问了不存在的属性;null 是有意设置的一个空值,表示'这里应该有东西,但我选择留空'。null 还可以用来手动释放内存,断开对象引用,帮助垃圾回收。"

然后看着面试官频频点头的样子,心里默念:这波,又稳了。


本文所有代码示例均来自课堂学习资料,真实可运行。

相关推荐
jump_jump2 小时前
流式 HTML:从 htmx 片段装配到浏览器原生增量渲染
javascript·性能优化·前端工程化
swipe3 小时前
正则表达式入门到进阶:从表单校验到手写模板引擎
前端·javascript·面试
kyriewen4 小时前
前端错误监控最全指南:捕获 JS 异常、Promise 拒绝、资源加载失败,附上报代码
前端·javascript·监控
大家的林语冰4 小时前
ESLint 近期动态大全,新版本正式发布,antfu 大佬推荐的插件也更新了!
前端·javascript·前端工程化
胡志辉5 小时前
深入浅出 call、apply、bind
前端·javascript·后端
十九画生8 小时前
parentID ``` JavaScript 是区分大小写的,所以这两个不是同一个字段。 第二,`parent` 没有声明。 应该先写: `
javascript
怕浪猫8 小时前
Electron 开发实战(十六):总结与展望|生态现状、框架对比、行业趋势与学习指南
前端·javascript·electron
ZengLiangYi9 小时前
批量导入 1000 条对话的性能优化实战
javascript·后端·架构
竹林81810 小时前
用 wagmi v2 + viem 监听合约事件时踩的坑,我花了两天才把"遗漏事件"修好
javascript