JavaScript 基础面试题大赏:从 map(parseInt) 到 NaN 的奇幻冒险
🌟 开场白:你以为你懂 JS?那试试这个经典面试题
console.log([1, 2, 3].map(parseInt)); // 答案是?
如果你脱口而出 [1, 2, 3] —— 恭喜你,成功掉进了 JS 最著名的“温柔陷阱”之一 😏。
今天我们就来彻底拆解这个看似简单却暗藏玄机的问题,并顺带聊聊 map、parseInt、NaN、字符串包装类等 JS 基础中的“魔鬼细节”。
在这里告诉大家一个非常厉害的网站-------MDN
✅ map和parseInt所有具体细节都可以去MDN文档里查找,里面都有详细解释
🔍 Part 1:Array.prototype.map() 到底怎么工作?
✅ MDN定义 :
map()方法创建一个新数组,这个新数组由原数组中的每个元素都调用一次提供的函数后的返回值组成
const numbers = [1, 2, 3];
const doubled = numbers.map(item => item * 2);
console.log(doubled); // [2, 4, 6]
console.log(numbers); // [1, 2, 3] —— 原封不动!
const arr = [1,2,3,4,5,6];
console.log(arr.map(item => item * item)); //[1,4,9,16,25,36]
⚠️ 关键细节:回调函数接收 三个参数!
arr.map((element, index, array) => { ... })
element:当前元素index:当前索引array:原数组本身
所以当你这么写:
[1, 2, 3].map(function(item, index, arr) {
console.log(item, index, arr);
return item;
});
输出是:
1 0 [1, 2, 3]
2 1 [1, 2, 3]
3 2 [1, 2, 3]
一切正常,对吧?
💣 Part 2:map(parseInt) 为什么不是 [1, 2, 3]?
🤯 问题来了:
console.log([1, 2, 3].map(parseInt)); // 实际输出:[1, NaN, NaN]
Why? 因为 parseInt 的签名是:
parseInt(string, radix)
string:要解析的值(会先转为字符串)radix:进制(2~36),可选但关键!
而 map 调用时传的是:
parseInt(element, index, array)
注意:[1, 2, 3] 是数字数组,所以 element 是数字 1、2、3,不是字符串!
但别担心——parseInt 内部会自动调用 .toString() 把第一个参数转成字符串再处理。
于是实际执行变成:
| 元素 | 调用方式 | 结果解释 |
|---|---|---|
1 | parseInt(1, 0) | → 转为 "1",radix=0 → 按 10 进制解析 → 1 ✅ |
2 | parseInt(2, 1) | → 转为 "2",但 1 进制非法(进制必须 ≥2)→ NaN ❌ |
3 | parseInt(3, 2) | → 转为 "3",但 二进制只认 0/1,"3" 无效 → NaN ❌ |
💡 关键补充:
parseInt(2, 1)等价于parseInt("2", 1)。
只要radix < 2或radix > 36,parseInt直接返回NaN—— 这是 ECMAScript 规范明确定义的行为!
📌 记住:radix = 0 是一个特殊值,表示“让 JS 自动推断进制”(如遇 "0x" 当十六进制,否则默认十进制)。在本例中,由于数字无前缀,故按 10 进制处理。
✅ 正确写法有哪些?
方案 1:显式指定进制
['1','2','3'].map(x => parseInt(x, 10)); // [1, 2, 3]
方案 2:用 Number(更简洁且类型安全)
['1','2','3'].map(Number); // [1, 2, 3]
💡 小贴士:
Number('1.1')→1.1(保留小数)
parseInt('1.1')→1(只取整数部分)
根据需求选择,别混用!
🧪 Part 3:深入 parseInt 的“迷惑行为大赏”
console.log(parseInt('108')); // 108
console.log(parseInt('八百108')); // NaN(开头非数字)
console.log(parseInt('108八百')); // 108(遇到非数字就停!)
console.log(parseInt(1314.520)); // 1314(先转字符串再解析)
✅ 规则总结:
- 从左到右扫描,遇到第一个非数字字符就停止
- 如果开头就不是数字 →
NaN - 自动忽略小数点后的部分(因为
.520不是整数)
parseInt(1314.520)→ 先转为"1314.52",然后从左解析,遇到小数点(非数字字符)停止 → 结果为1314
🌀 Part 4:NaN —— JS 中最“自恋”的数字
console.log(typeof NaN); // "number" 😱
console.log(NaN === NaN); // false !!!
❓ 为什么 NaN !== NaN?
因为 IEEE 754 标准规定:任何涉及 NaN 的比较都返回 false。
所以判断 NaN 要用:
Number.isNaN 或者 isNaN
Number.isNaN(value) // 推荐!不会类型转换
// 或
isNaN(value) // 会先尝试转数字,可能误判(如 isNaN("hello") → true)
isNaN("123") // false(因为 "123" 可转为 123)
isNaN(" ") // false(空格转为 0)
isNaN(null) // false(null → 0)
isNaN(undefined) // true
💡 强调:
Number.isNaN只有在值本身就是NaN时才返回true,更安全。
🧩 常见产生 NaN 的场景:
0 / 0 // NaN
Math.sqrt(-1) // NaN
"abc" - 10 // NaN
parseInt("hello") // NaN
undefined + 5 // NaN
💡 面试加分项:
NaN是唯一一个不等于自身的值!
🧩NaN 与它的“兄弟们”:Infinity 家族
说到 JS 的“特殊数字”,除了 NaN 这个独行侠,还有两位性格鲜明的兄弟:Infinity 和 -Infinity
来看这段代码:
console.log(0 / 0); // NaN
console.log(6 / 0); // Infinity
console.log(-6 / 0); // -Infinity
是不是很反直觉?数学老师说“除以零无意义”,但 JavaScript 偏偏要给你一个“有意义”的答案!
✅ 规则如下:
正数 / 0→Infinity负数 / 0→-Infinity0 / 0→NaN(因为结果不确定)
💡 小知识:
Infinity也是typeof === "number"!JS 的数字世界,比你想象的更“宽容”。
🔍 那么,Infinity 有什么用?
- 表示“超大值”或“溢出”
- 在算法中作为初始最大值/最小值(比如找数组最小值时设
let min = Infinity) - 判断是否溢出:
isFinite(value)可以检测一个数是否既不是Infinity也不是NaN
console.log(isFinite(100)); // true
console.log(isFinite(1/0)); // false
console.log(isFinite(NaN)); // false
🧩 趣味对比:
| 表达式 | 结果 | 类型 | 是否等于自身 |
|---|---|---|---|
0 / 0 | NaN | number | ❌ (NaN !== NaN) |
1 / 0 | Infinity | number | ✅ |
-1 / 0 | -Infinity | number | ✅ |
所以,NaN 是数字家族里唯一“不认亲”的叛逆小孩,而 Infinity 兄弟俩虽然极端,但至少讲道理 😂。
📦 Part 5:字符串是基本类型,为啥能调 .length?
let str = 'hello';
console.log(str.length); // 5
但 str 是 原始类型(primitive) ,不是对象啊?!
✨ 答案:自动装箱(Auto-boxing)
当你访问 'hello'.length 时,JS 引擎偷偷做了:
let str2 = new String('hello');
console.log(str2.length);
这就是所谓的 包装类(Wrapper Object) :
String包装字符串Number包装数字Boolean包装布尔值
🕵️♂️ 验证一下:
typeof 'hello' // "string"
typeof new String('hello') // "object"
但别担心,JS 会在操作完成后自动“拆箱”。
JS 引擎会在你访问原始类型的属性或方法时,临时创建一个包装对象,操作完成后立即丢弃——你无需也不应该手动创建或释放它们。这些临时包装对象由引擎自动管理,生命周期极短,开发者无需干预
🔤 Bonus:字符串长度那些事儿
console.log('a'.length); // 1
console.log('中'.length); // 1
console.log('𝄞'.length); // 2 ← emoji 占两个 UTF-16 单元!
console.log('👋'.length); // 2
⚠️ 注意:
.length返回的是 UTF-16 code units 数量,不是“字符个数”!
所以处理 emoji 或多字节字符时,要用:
[...'👋'].length // 1(用扩展运算符或 Array.from)
🧩 字符串截取:slice vs substring
| 方法 | 支持负数索引? | 参数顺序颠倒? |
|---|---|---|
slice(a,b) | ✅ | 不交换,返回空字符串 |
substring(a,b) | ❌(负数变 0) | ✅ 自动交换成小→大 |
"hello".slice(-3, -1) // "ll"
"hello".substring(-3, -1) // "" (相当于 substring(0,0))
"hello".slice(3, 1) // ""
"hello".substring(3, 1) // "el" (自动变成 substring(1,3))
💡 记忆口诀:
slice冷静守序,substring热情调换
🔍 Bonus 2:找“l”不迷路 —— indexOf vs lastIndexOf
你有没有想过,JS 是怎么在字符串里“找人”的?
let str = "hello";
console.log(str.indexOf('l')); // 2
console.log(str.indexOf('o')); // 4
console.log(str.lastIndexOf('l')); // 3
📌 规则很简单:
indexOf(char):从前往后找,返回第一个匹配项的索引lastIndexOf(char):从后往前找,返回最后一个匹配项的索引- 如果找不到?统统返回
-1(不是undefined,也不是null!)
💡 面试陷阱题:
"hello".indexOf('x') === false // ❌ 错!结果是 -1,而 -1 是 truthy!正确判断是否存在应写:
if (str.indexOf('x') !== -1) { ... } // 或更现代的写法: if (str.includes('x')) { ... }
🎯 实战场景:
- 检查文件扩展名:
filename.lastIndexOf('.') - 判断是否包含关键词(虽然现在多用
includes) - 在模板解析、路径处理中定位分隔符
顺便说一句:indexOf 不仅用于字符串,数组也有同名方法,行为几乎一致!
[1, 2, 3, 2].indexOf(2); // 1
[1, 2, 3, 2].lastIndexOf(2); // 3
所以,下次当你想找“那个 l 到底在哪”,记得 JS 给你准备了两个侦探:一个从开头查,一个从结尾翻!
🏁 总结:面试高频考点清单
| 知识点 | 面试常问 | 易错点 |
|---|---|---|
map(parseInt) | ✅ | 忽略 index 当 radix |
NaN 判断 | ✅ | 不能用 === |
| 字符串包装类 | ✅ | 原始类型为何有方法? |
parseInt 行为 | ✅ | 遇非数字停止 |
slice vs substring | ⚠️ | 负数和参数顺序 |
| emoji 长度 | ⚠️ | .length ≠ 字符数 |
0 / 0 vs n / 0 | ✅ | 混淆 NaN 与 Infinity |