JS 代码技巧 vol.2 — 20 个数组/对象/字符串/正则的实用技巧

0 阅读5分钟

JS 代码技巧 vol.2 — 20 个数组/对象/字符串/正则的实用技巧

小不的代码技巧系列第 2 期
主题:数组 · 对象 · 字符串 · 正则
前 5 期盘了异步、错误、性能——这期回到 JS 基础语法里的"隐藏好用"功能 😎
20 个技巧一次打包,每个都是"用过就回不去"那种。

哈喽哇!我是小不不简说的不~

作为一个在代码界"翻车"无数次的选手,我算是看明白了:坑这东西吧,要么不踩,踩就踩大的😂

今天这期,主打一个 数组 · 对象 · 字符串 · 正则——不整虚的,全是实战里哭出来的教训。看不看随你~反正翻车实录又不收钱

Ps:文末有惊喜(不是广告!)


一、数组(5 个)

1. Array.from 的三种神奇用法

// ① 类数组转数组
Array.from(document.querySelectorAll("div"));

// ② 生成序列
Array.from({ length: 5 }, (_, i) => i); // [0, 1, 2, 3, 4]

// ③ Set 去重
Array.from(new Set([1, 2, 2, 3])); // [1, 2, 3]

// ④ 第二个参数是 map,省一次遍历
// 大数组比 .map() 快 30%

坑在哪:第二个参数能省一次遍历,大数组场景比 .map() 快 30%


2. reduce 的妙用:转对象/分组/扁平化

// ① 计数
["a", "b", "a"].reduce((acc, k) => ((acc[k] = (acc[k] || 0) + 1), acc), {});
// { a: 2, b: 1 }

// ② 分组(手写 groupBy)
[1, 2, 3, 4].reduce((acc, n) => {
  (acc[n % 2] ||= []).push(n);
  return acc;
}, {});
// { 0: [2, 4], 1: [1, 3] }

// ③ 求最大
[1, 5, 3].reduce((a, b) => Math.max(a, b));

坑在哪:reduce 太强大,能 map 干的别用 reduce——可读性差。reduce 的真用武之地是"累积"逻辑(转对象、分组)。


3. 对象数组去重:按字段

const arr = [
  { id: 1, n: "a" },
  { id: 2, n: "b" },
  { id: 1, n: "c" },
];

// ✅ 按 id 去重,保留最后一个
const unique = Object.values(Object.fromEntries(arr.map((o) => [o.id, o])));

坑在哪new Set(arr) 只对原始值有效,对象数组直接 Set 全部留下(引用比较)。


4. 扁平化:flat / flatMap

[[1, [2, [3]]]].flat(Infinity); // [1, 2, 3]
[1, 2, 3].flatMap((n) => [n, n * 2]); // [1, 2, 2, 4, 3, 6]

// 比 reduce + concat 简洁

坑在哪flat(Infinity) 性能不如手写递归(深度大时栈会爆)。深度可控用 flat,超大深度手写迭代器


5. Object.groupBy / Map.groupBy(ES2024)

const groups = Object.groupBy(users, (u) => u.role);
// { admin: [...], user: [...] }

const map = Map.groupBy(users, (u) => u.role);
// Map 形式

坑在哪:Node 21+ / 现代浏览器支持,老项目用 reduce 自己写。


二、对象(5 个)

6. 解构赋值的高级玩法

// 重命名
const { name: userName } = user;

// 嵌套 + 默认值(用户可能没头像)
const { profile: { avatar: avatar = "/default.png" } = {} } = user;

// 剩余值
const { id, ...rest } = user;

// 动态 key
const key = "name";
const { [key]: value } = user;

坑在哪:嵌套解构遇到 undefined 路径会报错 Cannot read of undefined= {} 兜底


7. 对象展开:深浅拷贝的坑

const a = { x: 1, nested: { y: 2 } };
const b = { ...a };
b.nested.y = 100;
console.log(a.nested.y); // 100——浅拷贝

坑在哪{...obj} 只浅一层。{...obj} 只浅一层。{...obj} 只浅一层。深拷贝用 structuredClone(见 part 06 tip 17)。


8. Object.fromEntries 妙用

// 数组转对象
Object.fromEntries([
  ["a", 1],
  ["b", 2],
]); // { a: 1, b: 2 }

// 过滤对象
const filtered = Object.fromEntries(
  Object.entries(obj).filter(([k, v]) => v > 0),
);

// 转换 value
const upper = Object.fromEntries(
  Object.entries(obj).map(([k, v]) => [k, v.toUpperCase()]),
);

坑在哪Object.fromEntries 不能转 Map(Map 用 new Map(entries))。配合 entries/keys/values 三大件,对象操作基本不愁。


9. ?.?? 链式组合

const street = user?.profile?.address?.street ?? "未知街道";
// 嵌套 + 默认值,比 && + || 链清爽一万倍

坑在哪?.?? 是一对好搭档,用过的都说回不去


10. Object.freeze / seal / preventExtensions 对比

方法适用场景
preventExtensions锁住 schema
seal锁住结构
freeze不可变对象
const config = Object.freeze({ api: "x" });
config.api = "y"; // 静默失败(严格模式抛错)

坑在哪freeze 是浅的,嵌套对象要递归 freeze。Vue 3 的 reactive 不需要手动 freeze,reactivity 自动追踪。


三、字符串(5 个)

11. 模板字符串 + 标签模板

// 嵌套
const html = `
  <div>
    ${user ? `<span>${user.name}</span>` : "匿名"}
  </div>
`;

// 标签模板:自定义处理函数
function highlight(strings, ...values) {
  return strings.reduce(
    (acc, s, i) => acc + s + (values[i] ? `<mark>${values[i]}</mark>` : ""),
    "",
  );
}
highlight`你好 ${name}, 你今天 ${status}`;
// "你好 <mark>小不</mark>, 你今天 <mark>摸鱼中</mark>"

坑在哪:标签模板常用于 styled-components、GraphQL queries、HTML 转义。机制不是方案——手写 HTML 转义容易漏,用现成库


12. String.replace 第二个参数是函数

// 数字加千分位
"1234567".replace(/\B(?=(\d{3})+(?!\d))/g, ",");
// '1,234,567'

// 第二个参数是函数时,能拿到匹配细节
"Hello World".replace(/(\w+)\s(\w+)/, (_, g1, g2) => `${g2} ${g1}`);
// 'World Hello'

坑在哪:用函数形式比 $1 $2 替换更灵活,复杂替换都走函数形式


13. Intl.NumberFormat 国际化

new Intl.NumberFormat("zh-CN", { style: "currency", currency: "CNY" }).format(
  1234.5,
); // '¥1,234.50'

new Intl.NumberFormat("en-US").format(1234.5); // '1,234.5'
new Intl.NumberFormat("de-DE").format(1234.5); // '1.234,5'

坑在哪:千万别手写千分位+货币格式,Intl 是浏览器/Node 原生支持。每语言手写一套迟早翻车。


14. Intl.DateTimeFormat 国际化

new Intl.DateTimeFormat("zh-CN", {
  dateStyle: "long",
  timeStyle: "short",
  timeZone: "Asia/Shanghai",
}).format(new Date());
// '2026年7月14日 23:07'

new Intl.DateTimeFormat("en-US", {
  dateStyle: "full",
  timeStyle: "short",
}).format(new Date());
// 'Tuesday, July 14, 2026 at 11:07 PM'

坑在哪:时区是隐藏坑。后端传时间戳,前端按用户时区渲染——new Date() 默认是浏览器本地时区。


15. String.raw + 自定义标签函数

// 原始字符串(不转义)
String.raw`a\nb`; // 'a\\nb'  (字面输出 \n)

// 自定义标签:模板字符串预处理
function safe(strings, ...values) {
  return strings.raw.reduce(
    (acc, s, i) => acc + s + (values[i] !== undefined ? escape(values[i]) : ""),
    "",
  );
}
safe`<a href="${url}">链接</a>`; // 自动转义

坑在哪:手写 HTML 转义容易漏,用现成库(DOMPurify)更稳。标签函数是机制不是方案。


四、正则(5 个)

16. 命名捕获组:可读性拉满

// ❌ 数字下标
const m = "2026-07-14".match(/(\d{4})-(\d{2})-(\d{2})/);
const year = m[1]; // 不读正则不知道是啥

// ✅ 命名捕获
const m = "2026-07-14".match(/(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/);
const { year, month, day } = m.groups;
// { year: '2026', month: '07', day: '14' }

坑在哪:所有需要"提取字段"的正则都该用命名捕获,代码可读性 + 100%


17. 零宽断言:边界匹配

// (?=) 前瞻:后面是
"foo1 foo2".match(/\w+(?=\d)/g); // ['foo1', 'foo2']

// (?<=) 后顾:前面是
"$100 €200".match(/(?<=[$€])\d+/g); // ['100', '200']

// (?! ) 否定前瞻:后面不是

坑在哪:零宽断言不消耗字符比捕获组高效。需要"在 X 旁边的 Y"场景都该用。


18. matchAll 替代 match + exec 循环

// ❌ 反例:exec 循环
const re = /(\d+)/g;
let m;
while ((m = re.exec(str)) !== null) {
  console.log(m[1]);
}

// ✅ matchAll:一行
for (const m of str.matchAll(/(\d+)/g)) {
  console.log(m[1]);
}

坑在哪matchAll 必须加 g 标志,返回迭代器(不是数组),需要展开用 [...str.matchAll(re)]正则 exec 循环可以退役了


19. replace 特殊变量:& ` $'

// $&: 匹配到的整体
// $`: 匹配前的内容
// $': 匹配后的内容
// $1 $2 ...: 捕获组

"John Smith".replace(/John/, "$& Jr.");
// 'John Jr. Smith'

"John Smith".replace(/John/, "$`!!");
// '!! Smith'   (把"前面"塞到匹配位置)

坑在哪:用在 replace 第二个参数是字符串时。$' 引用匹配后内容——小心递归替换,可能死循环


20. 灾难性回溯(ReDoS):正则别瞎写

// ❌ 反例:嵌套量词 + 模糊匹配 = O(2^n) 回溯
/^(\w+)*$/.test("a".repeat(30) + "!");
// 浏览器直接卡死几秒

坑在哪正则写错能 DoS 服务——Node 端正则要警惕。复杂正则用工具(safe-regex / rxxr2)检测。灾难性回溯是面试爱问但生产更爱翻车的点,别等到线上炸了再修


📦 收个尾

这 20 个技巧浓缩一下:

  • 最高频踩坑:ReDoS 灾难性回溯、{...obj} 浅拷贝、matchAllg 标志
  • 最值得收藏Intl.NumberFormat 国际化、命名捕获组、标签模板
  • 核心思路:现代 API 能干的事别自己手写

学到了就是赚到了,犹豫徘徊等于白来~


写到最后

想要啥技巧?评论区甩个题目过来~

  • 你刚踩的坑
  • 项目里反复写的代码
  • 想搞清楚但一直懒得查的 API

小不看到…不一定回 😂 毕竟代码里翻车太多,腾不出手~

Ps:三连随缘,催更的会被打 😂