写着写着,就踩进了 JavaScript 的小坑

很多人学 JS,是从"能把需求写出来"开始的。

但真正在面试或者写复杂业务时,常见的几个小坑------数组的 mapNaNInfinity、字符串的包装类和长度问题------经常一起出来"围殴"你。

这篇文章不讲大而全,只用几组小实验,把它们串成一条线:

从数组遍历,到数字解析,再到字符串与 emoji 的长度。

一、数组不只是 formap 才是更现代的写法

最传统的遍历数组,往往是这样:

javascript 复制代码
const arr = [1, 2, 3, 4, 5, 6];
const result = [];
for (let i = 0; i < arr.length; i++) {
  result.push(arr[i] * arr[i]);
}
console.log(result); // [1, 4, 9, 16, 25, 36]

而在更现代的写法里,我们会让数组自带的方法来负责"遍历 + 映射":

javascript 复制代码
const arr = [1, 2, 3, 4, 5, 6];
console.log(arr.map(item => item * item)); 
// [1, 4, 9, 16, 25, 36]

特点:

  • 原数组不变,返回一个新数组
  • 更符合"声明式"的风格:告诉它"要做什么",而不是"怎么做"

理解 map 的回调函数签名也很重要:

javascript 复制代码
array.map(function (item, index, arr) {
  // item  当前元素
  // index 当前索引
  // arr   整个原数组
});

只要记住这三个参数的顺序,你在后面就能看懂更多"骚操作"。

二、当 map 遇上数字解析:NaN 是怎么溜进来的?

有了上面的铺垫,再看这一类代码就不陌生了:

javascript 复制代码
const arr = [1, 2, 3];

arr.map(function (item, index, source) {
  console.log(item, index, source);
  return item;
});

这一段可以帮助你记住参数顺序。

然后,在实际项目或面试题中,很容易有人写出类似:

javascript 复制代码
[1, 2, 3].map(parseInt);

这个例子本身已经很有名了,这里只强调两点:

  • 回调函数拿到的是 (item, index, arr)
  • 数字解析函数的签名是 (string, radix):要解析的字符串 + 进制

当两边的参数顺序撞在一起时,就有了进制被错误传入 的问题,于是 NaN 出现了。

关键不是记住"这题的答案",

而是要记住:数组方法的回调长什么样,别乱用现成函数往上一丢就图省事。

三、NaNInfinity:数字类型里的"异类"

在处理数字时,还有两个非常容易被忽视的存在:

javascript 复制代码
console.log(0 / 0);   // NaN
console.log(6 / 0);   // Infinity
console.log(-6 / 0);  // -Infinity

以及:

javascript 复制代码
console.log(parseInt("108"));       // 108
console.log(parseInt("八百108"));   // NaN
console.log(parseInt("108八百"));   // 108
console.log(parseInt(1314.520));    // 1314

可以总结出几个有用的直觉:

  • NaN

    表示"这不是一个合法的数字结果",典型场景是:

    • 0 / 0
    • 完全看不懂的字符串解析(比如一上来就是汉字)
  • Infinity / -Infinity

    表示正负无穷大,例如除以 0。

  • parseInt 的解析习性

    • 从左往右看,一开始就看不懂 → 整体 NaN
    • 先看懂了一段,中途遇到不认识的字符 → 前面合法的部分照算
    • 遇到小数 → 小数点后面直接不要

再加上一句经典但反直觉的事实:

javascript 复制代码
typeof NaN === "number"; // true

这就是为什么很多 JS 教程会专门开一小节来讲"特殊数字类型"。

四、看起来"一切皆对象",其实 JS 在背后帮你擦屁股

有一个常被忽略、但又极其常用的能力:

javascript 复制代码
"hello".length;         // 5
(520.1314).toFixed(2);  // "520.13"

从传统面向对象的思路看:

  • 字符串字面量、数字字面量都属于原始值
  • 原始值照理说不是对象,不能随便点属性、调方法

但在这门语言里,你天天在这么写,而且完全没报错。

原因是引擎偷偷给你做了**"包装"**:

  • 原始字符串 → 临时变成 String 对象
  • 原始数字 → 临时变成 Number 对象
  • 原始布尔值 → 临时变成 Boolean 对象

可以用一组简化版的伪操作来理解:

javascript 复制代码
var str = "hello";

str.length; 

// 底层会做类似下面的事:
var strObj = new String(str);
console.log(strObj.length); // 5
strObj = null;              // 用完扔掉

console.log(typeof str);    // "string"(原始类型没改变)

也就是说:

  • 表面上是"统一风格,一切皆能点属性、调方法"
  • 实际上是引擎在背后帮你 new 来 new 去

这也解释了为什么这门语言经常被说"很傻瓜化":

为了让你写起来爽,它会帮你兜很多底。

五、字符串长度与 emoji:肉眼看到的"一个"不等于 length === 1

再来看一组和字符串相关的实验:

arduino 复制代码
js
console.log("a".length);   // 1
console.log("中".length);  // 1
console.log("𝄞".length);   // 2(看起来一个符号,却占了两个长度单位)

在这门语言里,字符串底层使用 UTF-16 编码

  • 大部分常见字符用 一个 16 位单元 表示 → length 加 1
  • 某些生僻字和 emoji 用 两个 16 位单元 表示 → length 加 2

再配合一段稍微综合一点的示例:

javascript 复制代码
const str = " Hello, 世界! 👋  ";

console.log(str.length);                      // 包含空格、中文、emoji 在内的长度
console.log(str[1]);                          // 访问第二个 UTF-16 单元
console.log(str.charAt(1), str.charAt(1) == str[1]); // 在常规字符上二者表现一致
console.log(str.slice(1, 6));                 // 截取 [1, 6) 区间
console.log(str.substring(1, 6));             // 在这个用法下和 slice 表现一样
```](cascade:incomplete-link)

你可以得到两个非常有用的结论:

  • length 表示的是UTF-16 单元数量,不是"肉眼看到的字符个数"
  • 对英文、常见汉字,大多数时候可以"假装没区别"
  • 一旦大量使用 emoji 或特殊字符,索引和截取就可能和视觉表现错位

在做以下需求时,一定要记住这一点:

  • 限制"输入最多 N 个字符"
  • 截断字符串用于展示(例如列表项缩略显示)
  • 按"字符数"计费、统计、对齐

否则,emoji 往往会成为你 UI 中最顽皮的那一块。

六、把这些零散知识点连成一条线

回头看看前面的几个小实验,它们其实在回答同一类问题:

"这门语言,为了让你写起来看起来简单,到底在底层帮你做了多少事?"

  • 数组方法
    map 不只是简化了 for 循环,还规定了固定的回调参数顺序,
    一不留神用错现成函数,就会引入莫名其妙的 NaN
  • 数字解析与特殊值
    parseInt 的解析规则、NaNInfinity 的存在,都在提醒你:
    "看起来是数字,其实里面有很多状态要区分"。
  • 包装类
    "hello".length(520.1314).toFixed(2) 这种写法之所以成立,
    是因为底层在帮你悄悄构造临时对象。
  • 字符串与编码
    length 和字符视觉上的"个数"不总是对得上的,
    emoji 是检验你有没有意识到这一点的最好测试用例。

当你愿意停下来,用几分钟时间亲手敲一遍这些代码,

并且追问每一行输出背后的"为什么",

你就已经不只是"会写这门语言",

而是在慢慢"理解这门语言"。

相关推荐
ERP老兵-冷溪虎山1 小时前
Python/JS/Go/Java同步学习(第五十篇半)四语言“path路径详解“对照表: 看完这篇定位文件就通透了(附源码/截图/参数表/避坑指南)
java·javascript·python·golang·中医编程·编程四语言同步学·path路径详解
月亮慢慢圆1 小时前
首字母模糊匹配
前端
一个有理想的摸鱼选手1 小时前
CesiumLite-在三维地图中绘制3D图形变得游刃有余
前端·gis·cesium
一千柯橘1 小时前
Three.js 坐标系完全入门:从“你在哪”到“你爸在哪”都讲清楚了
前端
独角仙梦境1 小时前
同事:架构太复杂了,源码文件找半天。 我:源码溯源了解一下?
前端·vue.js
八哥程序员1 小时前
从border-image 到 mask + filer 实现圆角渐变边框
前端·css
ChangYo1 小时前
解决网页前端中文字体包过大的几种方案
前端
车前端1 小时前
现代 Nginx 优化实践:架构、配置与性能调优
前端·nginx
槁***耿1 小时前
前端路由守卫
前端