前言:一个让面试官疯狂追问的JS“陷阱”
在 JavaScript 面试中,const 和 NaN 是两个高频考点,但很多开发者对它们的理解仍然停留在表面。比如:
- “
const不是常量吗?为什么对象还能改?” - “
NaN明明是Not a Number,为什么typeof NaN返回'number'?”
来看一个典型的代码示例,让问题更直观:
const person = { name: "Alice" };
person.name = "Bob"; // ✅ 居然可以改!
const num = 3/0; // 🤔 这是啥?
console.log(num); // NaN
console.log(num === NaN); // false(WTF?!)
比隔壁玛丽奶奶做的苹果派还要糟糕的问题来了:
const到底能不能改? 为什么对象属性可以变,但直接赋值会报错?NaN为什么自己不等于自己? 它到底是数字还是非数字?
本文将深入 JS 存储机制(栈与堆) ,带你揭开 const 的“伪常量”真相,并拆解 NaN 这个让无数程序员崩溃的“奇葩”。
(友情提示:看完这篇文章,你不仅能完美回答面试官的刁钻问题,还能用底层原理让他眼前一亮!)
const:什么TMD叫TMD惊喜!
在 JavaScript 里,const 表面上是个看上去像个老实人,官方定义是 “常量,不可重新赋值”,但他就像超兽武装里的老好人鬼谷长老一样不老实,一不留神就要把冥王和雪皇全灭了。
要想明白它为什么这么不老实,那就要从JavaScript存储值的机制说起来了
JavaScript是怎么存储值的?
JavaScript运行时采用双轨制内存管理机制:
- 栈内存(Stack)
- 堆内存(Heap)
栈内存主要存储原始数据类型(Numeric、String、Boolean、null、undefined、symbol)
而堆内存主要存储复杂数据类型(Object)
例如下面一段代码的存储方式是这样的:
let age = 25; // 基本类型 -> 栈
const name = "Alice"; // 基本类型 -> 栈
const person = { // 引用类型
name: "Bob", // person对象存储在堆中
age: 30 // 栈中存储的是它的引用地址
};
const hobbies = ["coding", "music"]; // 数组也是引用类型
而const能够修改的秘密就在这里:
基本数据类型存储
基本数据类型(如数字、字符串、布尔值等)的存储机制具有以下特点:
- 值直接存储在栈内存中
- 变量名与值在栈中形成直接映射关系
- 访问时无需间接寻址,直接从栈中读取实际值
- 每个变量都独立存储自己的数据副本
例如:
let a = 10; // 数值10直接存储在栈内存的a变量位置
let b = a; // 栈中会创建b的新位置,并直接存入数值10的副本,后续给a的值修改,b也不会被影响
这种存储方式的特点:
- 访问速度快(直接存取)
- 内存占用固定(已知大小)
- 生命周期与执行上下文同步(随上下文创建/销毁)
复杂数据类型(Object)存储
而复杂数据类型(Object)却不同:
它们在栈中存储的不是具体的值,而是一串地址,由这一串地址再找到具体的值。
为什么对象不直接存到栈中?
栈的特性限制
- 栈内存适合存储固定大小、生命周期短的数据(如基本类型)。
- 对象的大小通常是动态的、不可预知的(比如一个 user 对象可能随时新增属性)。
- 如果直接将大对象存入栈中,容易导致 栈溢出(Stack Overflow)(栈内存空间有限,默认约 1MB~8MB,依环境而定)。
堆的灵活性
- 堆内存专为动态分配的大数据设计,空间远大于栈(通常可达数GB)。
- 对象存储在堆中后,栈只需保存一个固定的引用地址(类似门牌号),无论对象多大,地址长度不变(例如 64 位系统中的 8 字节)。
为什么 const 在对象中能修改值?
const person = { name: "Alice" };
person.name = "Bob";
这段代码之所以成功就是因为在栈内存中,person存储的只是一个指向的地址,而我们修改的,是堆内存中的值,const的不可修改实际指的是不可修改栈内存中的值,而当我们利用person.name来修改内容时,并没有修改栈内存的指向地址,所以我们的操作是可行的!
接下来,我们来看一个例子,看看你是否理解了这个表里不一的东西:
const Friends = [
{
name: "John",
age: 20,
gender: "male"
},
{
name: "Mary",
age: 21,
gender: "female"
},
];
Friends.push({
name: "Bob",
age: 22,
gender: "male"
});
const newFriends = Friends;
newFriends.push({
name: "Sue",
age: 23,
gender: "female"});
console.log(newFriends);
console.log(Friends);
那么它会输出什么呢? 会输出:
[
{ name: 'John', age: 20, gender: 'male' },
{ name: 'Mary', age: 21, gender: 'female' },
{ name: 'Bob', age: 22, gender: 'male' },
{ name: 'Sue', age: 23, gender: 'female' }
]
[
{ name: 'John', age: 20, gender: 'male' },
{ name: 'Mary', age: 21, gender: 'female' },
{ name: 'Bob', age: 22, gender: 'male' },
{ name: 'Sue', age: 23, gender: 'female' }
]
因为const newFriends = Friends 传递的只是Friends在栈内存的地址,也就是说newFriends指向了Friends所指向的同一块堆存储的内容,所以当newFriends和Friends任何一方修改时,都会影响堆存储的内容,双方的内容都会改变。
注意:
1.const 对于简单数据,复杂数据类型虽然表现不一致,但内存栈中的值都不会发生改变
2.简单数据类型的值都是直接存储在Stack中,复杂数据类型在Stack中存储的是地址,地址指向堆中的值
NaN?再装x让你飞起来!(神鹰哥黑手表情)
相信大家学习JavaScript都遇到了NaN这一奇怪的表达,那咱就唠唠这到底是怎么个事儿
NaN是啥?创作初衷?
NaN看起来神神秘秘,两个N夹个a 跟老母猪戴胸罩一套又一套似的,其实就是三个词:Not a Number,不是个数字,它是一种表达,设计来表示不合理的无效的数学运算结果,比如:
0/0
Math.sqrt(-1)
100* "hello"
给 -1 取个根号,给0除以个0,还有给 100 乘以个 hello 的,你说这些运算离不离谱?这要都能算出来数字,那还真是诺贝尔生物学奖了,那都打破生殖隔离了。
NaN 的特性
(1)NaN 是唯一不等于自身的值
console.log(NaN === NaN); // false
这是 JavaScript 中最特殊的一点,任何值(包括 NaN 本身)与 NaN 进行严格相等比较时都会返回 false。因此,必须使用专门的函数(如 isNaN() 或 Number.isNaN())来检测 NaN。
(2)NaN 的类型是 number
console.log(typeof NaN); // "number"
虽然 NaN 表示 "不是一个数字",但它仍然是 Number 类型的一部分。
(3)NaN 是全局对象的属性
console.log(window.NaN); // NaN(浏览器环境)
console.log(globalThis.NaN); // NaN(Node.js / 通用环境)
2. 何时会得到 NaN?
(1)数学运算失败
当数学运算的操作数无法转换为有效数字时,返回 NaN:
console.log(10 * "hello"); // NaN(数字 × 非数字字符串)
console.log(0 / 0); // NaN(0 ÷ 0 在数学上未定义)
console.log(Math.sqrt(-1)); // NaN(负数的平方根)
(2)强制类型转换失败
使用 Number()、parseInt()、parseFloat() 转换非数字字符串:
console.log(Number("123abc")); // NaN(部分解析失败)
console.log(parseInt("Hello")); // NaN(完全无法解析)
console.log(+undefined); // NaN(undefined 不能转数字)
(3)NaN 参与运算
如果运算中已经包含 NaN,结果通常也是 NaN:
console.log(NaN + 10); // NaN
console.log(NaN * 100); // NaN
如何检测 NaN?
由于 NaN 不等于自身,不能直接用 === 判断,必须借助以下方法:
(1)isNaN()(不推荐)
- 先尝试将参数转换为数字,再检查是否为
NaN。 - 问题:可能会误判(如
isNaN("hello")返回true,因为"hello"无法转数字)。
console.log(isNaN(NaN)); // true
console.log(isNaN("hello")); // true(误判)
console.log(isNaN(123)); // false
(2)Number.isNaN()(推荐)
- 严格检查,仅当值确实是
NaN时才返回true。 - 不会误判非数字值。
console.log(Number.isNaN(NaN)); // true
console.log(Number.isNaN("hello")); // false(正确)
console.log(Number.isNaN(123)); // false
(3)利用 NaN 不等于自身的特性
function isNaNValue(x) {
return x !== x;
}
console.log(isNaNValue(NaN)); // true
console.log(isNaNValue(123)); // false
浅浅地总结一下NaN
NaN表示无效的数学运算结果,如0/0、"abc" * 10。NaN是唯一不等于自身的值,不能用=== NaN检测。- 推荐使用
Number.isNaN()检测NaN,避免误判。 - 避免
NaN的方法包括输入校验、默认值、Number.isFinite()检查等。
总结
通过这篇文章相信大家对于 const 的背刺原因和NaN这个故作神秘的🔥东西有了一定的理解了吧,看了我这篇救世良方,相信你一定不会被面试官拷打的那么狠了,当你兴致勃勃跟面试官聊完了const和NaN,说不定你还能拉着他打一把激情四射的瓦罗兰特呢!