JS 代码技巧 vol.2 --- 20 个数组/对象/字符串/正则的实用技巧
小不的代码技巧系列第 2 期
主题:数组 · 对象 · 字符串 · 正则
前 5 期盘了异步、错误、性能------这期回到 JS 基础语法里的"隐藏好用"功能 😎
20 个技巧一次打包,每个都是"用过就回不去"那种。
哈喽哇!我是小不 ,不简说的不~
作为一个在代码界"翻车"无数次的选手,我算是看明白了:坑这东西吧,要么不踩,踩就踩大的😂
今天这期,主打一个 数组 · 对象 · 字符串 · 正则------不整虚的,全是实战里哭出来的教训。看不看随你~反正翻车实录又不收钱
Ps:文末有惊喜(不是广告!)
一、数组(5 个)
1. Array.from 的三种神奇用法
js
// ① 类数组转数组
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 的妙用:转对象/分组/扁平化
js
// ① 计数
["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. 对象数组去重:按字段
js
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
js
[[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)
js
const groups = Object.groupBy(users, (u) => u.role);
// { admin: [...], user: [...] }
const map = Map.groupBy(users, (u) => u.role);
// Map 形式
坑在哪:Node 21+ / 现代浏览器支持,老项目用 reduce 自己写。
二、对象(5 个)
6. 解构赋值的高级玩法
js
// 重命名
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. 对象展开:深浅拷贝的坑
js
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 妙用
js
// 数组转对象
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. ?. 和 ?? 链式组合
js
const street = user?.profile?.address?.street ?? "未知街道";
// 嵌套 + 默认值,比 && + || 链清爽一万倍
坑在哪 :?. 和 ?? 是一对好搭档,用过的都说回不去。
10. Object.freeze / seal / preventExtensions 对比
| 方法 | 增 | 删 | 改 | 适用场景 |
|---|---|---|---|---|
preventExtensions |
❌ | ✅ | ✅ | 锁住 schema |
seal |
❌ | ❌ | ✅ | 锁住结构 |
freeze |
❌ | ❌ | ❌ | 不可变对象 |
js
const config = Object.freeze({ api: "x" });
config.api = "y"; // 静默失败(严格模式抛错)
坑在哪 :freeze 是浅的,嵌套对象要递归 freeze。Vue 3 的 reactive 不需要手动 freeze,reactivity 自动追踪。
三、字符串(5 个)
11. 模板字符串 + 标签模板
js
// 嵌套
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 第二个参数是函数
js
// 数字加千分位
"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 国际化
js
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 国际化
js
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 + 自定义标签函数
js
// 原始字符串(不转义)
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. 命名捕获组:可读性拉满
js
// ❌ 数字下标
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. 零宽断言:边界匹配
js
// (?=) 前瞻:后面是
"foo1 foo2".match(/\w+(?=\d)/g); // ['foo1', 'foo2']
// (?<=) 后顾:前面是
"$100 €200".match(/(?<=[$€])\d+/g); // ['100', '200']
// (?! ) 否定前瞻:后面不是
坑在哪 :零宽断言不消耗字符 ,比捕获组高效。需要"在 X 旁边的 Y"场景都该用。
18. matchAll 替代 match + exec 循环
js
// ❌ 反例: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 特殊变量:& ` $'
js
// $&: 匹配到的整体
// $`: 匹配前的内容
// $': 匹配后的内容
// $1 $2 ...: 捕获组
"John Smith".replace(/John/, "$& Jr.");
// 'John Jr. Smith'
"John Smith".replace(/John/, "$`!!");
// '!! Smith' (把"前面"塞到匹配位置)
坑在哪 :用在 replace 第二个参数是字符串时。$' 引用匹配后内容 ------小心递归替换,可能死循环。
20. 灾难性回溯(ReDoS):正则别瞎写
js
// ❌ 反例:嵌套量词 + 模糊匹配 = O(2^n) 回溯
/^(\w+)*$/.test("a".repeat(30) + "!");
// 浏览器直接卡死几秒
坑在哪 :正则写错能 DoS 服务 ------Node 端正则要警惕。复杂正则用工具(safe-regex / rxxr2)检测 。灾难性回溯是面试爱问但生产更爱翻车的点,别等到线上炸了再修。
📦 收个尾
这 20 个技巧浓缩一下:
- 最高频踩坑 :ReDoS 灾难性回溯、
{...obj}浅拷贝、matchAll漏g标志 - 最值得收藏 :
Intl.NumberFormat国际化、命名捕获组、标签模板 - 核心思路:现代 API 能干的事别自己手写
学到了就是赚到了,犹豫徘徊等于白来~
写到最后
想要啥技巧?评论区甩个题目过来~
- 你刚踩的坑
- 项目里反复写的代码
- 想搞清楚但一直懒得查的 API
小不看到...不一定回 😂 毕竟代码里翻车太多,腾不出手~
Ps:三连随缘,催更的会被打 😂