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

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} 浅拷贝、matchAllg 标志
  • 最值得收藏Intl.NumberFormat 国际化、命名捕获组、标签模板
  • 核心思路:现代 API 能干的事别自己手写

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


写到最后

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

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

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

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

相关推荐
小小编程路1 小时前
C++ 块作用域详解:从基础到实战
前端·javascript·vue.js
虚惊一场2 小时前
MarkView:纯前端 Markdown 实时预览工具的架构设计
前端·javascript·vue.js
冰暮流星2 小时前
javascript之对象的继承
javascript
hunterandroid2 小时前
[鸿蒙从零到一] ArkUI 常用布局实战:Column、Row、Stack 与 Flex
前端
海上彼尚2 小时前
Nodejs也能写Agent - 17.LangGraph篇 - Checkpointer 与 Memory
前端·javascript·人工智能·langchain·node.js
盛夏绽放2 小时前
Hermes Agent 全栈安装、配置与Web端架构深度解析(官方命令版·最终优化)
前端·架构·hermes
早期的虫儿有鸟吃2 小时前
Vue3
前端·javascript·vue.js
hqk2 小时前
我给 AI 造了一个原型工具:AIWireframeViewer V1
前端·ai编程·vibecoding
Highcharts.js2 小时前
商业授权与开源图表库如何选择?
前端·javascript·开源·highcharts·chart.js·echarts.js