面试官最爱问:const真的不能改吗?介绍一下NaN这个🔥东西?

145 阅读7分钟

前言:一个让面试官疯狂追问的JS“陷阱”

在 JavaScript 面试中,constNaN 是两个高频考点,但很多开发者对它们的理解仍然停留在表面。比如:

  • 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?!)

比隔壁玛丽奶奶做的苹果派还要糟糕的问题来了:

  1. const 到底能不能改?  为什么对象属性可以变,但直接赋值会报错?
  2. NaN 为什么自己不等于自己?  它到底是数字还是非数字?

本文将深入 JS 存储机制(栈与堆) ,带你揭开 const 的“伪常量”真相,并拆解 NaN 这个让无数程序员崩溃的“奇葩”。

(友情提示:看完这篇文章,你不仅能完美回答面试官的刁钻问题,还能用底层原理让他眼前一亮!)

const:什么TMD叫TMD惊喜!

在 JavaScript 里,const 表面上是个看上去像个老实人,官方定义是 “常量,不可重新赋值”,但他就像超兽武装里的老好人鬼谷长老一样不老实,一不留神就要把冥王和雪皇全灭了。

要想明白它为什么这么不老实,那就要从JavaScript存储值的机制说起来了

JavaScript是怎么存储值的?

JavaScript运行时采用双轨制内存管理机制:

  1. 栈内存(Stack)
  2. 堆内存(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"]; // 数组也是引用类型

image.png

image.png

而const能够修改的秘密就在这里:

基本数据类型存储

基本数据类型(如数字、字符串、布尔值等)的存储机制具有以下特点:

  1. 直接存储在栈内存
  2. 变量名与值在栈中形成直接映射关系
  3. 访问时无需间接寻址,直接从栈中读取实际值
  4. 每个变量都独立存储自己的数据副本

例如:

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来修改内容时,并没有修改栈内存的指向地址,所以我们的操作是可行的!

4a679daabe894daf8a7b5786375ac806.gif

接下来,我们来看一个例子,看看你是否理解了这个表里不一的东西:

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所指向的同一块堆存储的内容,所以当newFriendsFriends任何一方修改时,都会影响堆存储的内容,双方的内容都会改变。

image.png

注意

1.const 对于简单数据,复杂数据类型虽然表现不一致,但内存栈中的值都不会发生改变

2.简单数据类型的值都是直接存储在Stack中,复杂数据类型在Stack中存储的是地址,地址指向堆中的值

NaN?再装x让你飞起来!(神鹰哥黑手表情)

相信大家学习JavaScript都遇到了NaN这一奇怪的表达,那咱就唠唠这到底是怎么个事儿

微信图片_20250512074648.jpg

NaN是啥?创作初衷?

NaN看起来神神秘秘,两个N夹个a 跟老母猪戴胸罩一套又一套似的,其实就是三个词:Not a Number,不是个数字,它是一种表达,设计来表示不合理的无效的数学运算结果,比如:

0/0

Math.sqrt(-1)

100* "hello"

给 -1 取个根号,给0除以个0,还有给 100 乘以个 hello 的,你说这些运算离不离谱?这要都能算出来数字,那还真是诺贝尔生物学奖了,那都打破生殖隔离了。

849907FD83842BBAA50BC023E5C9CFD5.jpg

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,说不定你还能拉着他打一把激情四射的瓦罗兰特呢!

7592e9084b36acaf148439cd3ad98d1001e99c1e.jpg