1.该自动化什么——判断标准
自动化就是让代码代替人去做原本手工重复的工作。它不一定是什么大程序。从每周收到的销售记录里算出各品类合计,或者把从各处收集来的名单里的空格和重复项整理干净,这样几十行的代码就已经是很好的自动化了。
不过,并不是所有事情都要写成代码。如果一件事只做一次、十分钟就能做完,写代码再检查的时间反而可能更长。反过来,一次要花十分钟的事如果每周都做,一年就是520分钟,大约9个小时。按同样的规则反复进行、数量大、手工做又容易出错的工作,越值得自动化。
规则是否明确也很重要。“只保留电话号码里的数字,再改成 010-0000-0000 的样子”这种规则很容易写成代码。但像“把别扭的句子改得自然一些”这种需要判断的工作,更适合由人或 AI 助手来审阅。第1讲说过,能用语言一条一条写清楚的规则,就能写成代码,这个想法在这里同样适用。
| 标准 | 问问自己 | 倾向于自动化的信号 |
|---|---|---|
| 重复 | 多久做一次 | 每天、每周、每月都会重复 |
| 规则 | 能否用语言写出处理规则 | 连例外在内几行就能说清 |
| 数量 | 一次要处理多少条 | 几十条以上,用眼睛看很吃力 |
| 出错代价 | 出错了会有多麻烦 | 手工做时经常漏项、打错字 |
| 变化 | 输入的样子是否常变 | 列的顺序和格式基本不变 |
2.把逗号分隔的文本变成表格
从电子表格软件导出的数据,或者通过聊天软件收到的列表,常常是一行一条记录、各列用逗号隔开的样子。这种格式叫 CSV(逗号分隔值)。第一行通常是列名(表头),从第二行开始才是真正的记录。
用代码转换的步骤很简单。先用第7讲学过的 split("\n") 把文本分成行,再用 split(",") 把每一行分成列。然后用第6讲的对象给每一列起个名字,就得到了“对象数组”,也就是代码里的表格。为了不让开头或结尾的空行变成空记录,最好先做一次 trim()。
这里有一个重要的陷阱:split 分出来的每一列都是字符串。"10" 看起来像数字,其实是字符串,直接相加就会像第2讲看到的那样被拼接起来。数量、金额这样的列必须先用 Number() 转换,计算才会正确。
slice(1) 跳过。const text = `日期,品名,数量,单价
2026-03-01,铅笔,10,500
2026-03-01,笔记本,3,2000
2026-03-02,铅笔,5,500
2026-03-02,橡皮,4,300`;
const lines = text.trim().split("\n");
console.log(lines[0].split(","));
const rows = lines.slice(1).map((line) => {
const c = line.split(",");
return {
date: c[0],
item: c[1],
qty: Number(c[2]),
price: Number(c[3]),
};
});
console.log(rows.length);
console.log(rows[0]);[ '日期', '品名', '数量', '单价' ]
4
{ date: '2026-03-01', item: '铅笔', qty: 10, price: 500 }+ 是拼接。只要混进了非数字的字符,Number 就会返回 NaN。const c = "2026-03-01,铅笔,10,500".split(",");
console.log(c[2] + c[3]);
console.log(Number(c[2]) * Number(c[3]));
console.log(Number("10个"));10500 5000 NaN
3.合计、平均值和分组合计
有了表格,就可以用第4讲的累加模式求合计:先把一个变量设为0,再逐条遍历记录往上加。平均值是合计除以记录条数,但如果记录是0条,就会变成除以0,得到 NaN,所以要先检查。如果小数位太长,可以像 Math.round(x * 10) / 10 这样事先定好保留几位。
像“各品类合计”“各部门费用”这样按组分别相加,叫作分组合计。先建一个空对象,每条记录用所属组的名字作键,把值加上去。第一次出现的键,值是 undefined,所以要像 (合计[键] || 0) + 值 这样让它从0开始,这是关键技巧。这相当于用几行代码亲手做一遍电子表格里数据透视表的工作。
算完之后,一定要手工验算其中一个数字。下面的例子中,铅笔是10支×500韩元加5支×500韩元,应该是7,500韩元。哪怕只亲手核对一组,也能立刻发现选错列、忘记把字符串转成数字之类的错误。
const rows = [
{ item: "铅笔", qty: 10, price: 500 },
{ item: "笔记本", qty: 3, price: 2000 },
{ item: "铅笔", qty: 5, price: 500 },
{ item: "橡皮", qty: 4, price: 300 },
];
let total = 0;
let qtySum = 0;
const byItem = {};
for (const r of rows) {
const amount = r.qty * r.price;
total += amount;
qtySum += r.qty;
byItem[r.item] = (byItem[r.item] || 0) + amount;
}
console.log("总销售额", total);
console.log("平均数量", qtySum / rows.length);
console.log(byItem);总销售额 14700
平均数量 5.5
{ '铅笔': 7500, '笔记本': 6000, '橡皮': 1200 }const text = `部门,金额
销售,32000
研发,15000
行政,9000
销售,18000
研发,26000`;
const sums = {};
for (const line of text.split("\n").slice(1)) {
const [team, money] = line.split(",");
sums[team] = (sums[team] || 0) + Number(money);
}
console.log(sums);
let best = null;
for (const [team, sum] of Object.entries(sums)) {
if (best === null || sum > best[1]) {
best = [team, sum];
}
}
console.log("花得最多的部门", best[0]);{ '销售': 50000, '研发': 41000, '行政': 9000 }
花得最多的部门 销售- 第1步 把文本分成行,跳过表头。
- 第2步 把每一行按逗号分开,取出部门和金额,金额用
Number转换。代码里的const [team, money] = …是把分出来的数组的第一个、第二个值依次放进两个名字里的语法(解构赋值)。 - 第3步 在空对象里以部门名为键,把金额加上去。
- 第4步 用
Object.entries把对象变成[部门, 合计]对组成的数组,遍历时记住最大的那个。 - 验算:销售是 32,000 + 18,000 = 50,000韩元,确认和代码的结果一致。
4.整理文本——空格、空行、重复项
多人手工录入的名单里,常常混着首尾空格、多打的空格、空行,以及同一个人出现两次的行。肉眼看起来一样,但对计算机来说 "小明" 和 "小明 " 是不同的字符串,不整理的话,去重和查找都做不好。
整理规则通常分三步:用 trim() 去掉首尾空格,用 replace(/\s+/g, " ") 把连续的空白缩成一个空格,再用 filter 去掉空字符串。\s+ 是表示“一个或多个空白字符”的正则表达式,第7讲里尝过一点。如果正则表达式让你犯迷糊,可以先把例句放进正则表达式测试器,看看它到底匹配到什么。
去重用 Set 很方便。Set 是同一个值只装一次的集合,用 [...new Set(数组)] 再展开成数组,就能按第一次出现的顺序去掉重复项。像电子邮件地址这样通常不区分大小写的值,要先用 toLowerCase() 统一,再去重。把整理前后的条数一起打印出来,就能马上看出去掉了几条。
const raw = [
" 小明 ",
"王 芳",
"小明",
"",
"张伟 ",
"王 芳",
];
const cleaned = raw
.map((s) => s.trim().replace(/\s+/g, " "))
.filter((s) => s !== "");
console.log(cleaned);
const unique = [...new Set(cleaned)];
console.log(unique);
console.log(raw.length, "→", unique.length);[ '小明', '王 芳', '小明', '张伟', '王 芳' ] [ '小明', '王 芳', '张伟' ] 6 → 3
const emails = ["[email protected]", "[email protected] "];
const norm = emails.map((e) => e.trim().toLowerCase());
console.log([...new Set(norm)]);[ '[email protected]' ]
5.统一电话号码和日期格式
同一个电话号码也会以 010-1234-5678、01012345678、010 1234 5678 等各种样子出现(这里以 01 开头的11位手机号为例)。统一格式最可靠的办法是“只留下数字,再重新拼装”。replace(/\D/g, "") 会删掉所有非数字字符。如果剩下的数字是11位并且以 01 开头,就按3-4-4切开,加上连字符。
不符合规则的值,不要硬改,最好返回 null 之类表示“无法处理”的结果。这样之后才能把这些行集中起来,由人来确认。如果自动化把不认识的值改成看似合理的样子,错误数据就会悄悄混进去,这比手工犯的错更难发现。
日期也五花八门,比如 2026.3.5.、2026/03/05、 2026-3-15 。先去掉空格和末尾的点,再按 .、/、- 中的任意一种分开,然后用 padStart(2, "0") 把月和日补成两位,变成 2026-03-05 的样子。这种格式即使按字符串排序,也正好是日期顺序,很方便。下面的函数只检查月份1到12、日期1到31,所以拦不住2月30日这样的日期。这样的局限要用注释写下来。
null,另行确认。function formatPhone(s) {
const d = s.replace(/\D/g, "");
if (d.length !== 11 || !d.startsWith("01")) {
return null;
}
return d.slice(0, 3) + "-" + d.slice(3, 7) +
"-" + d.slice(7);
}
const inputs = [
"010-1234-5678",
"01012345678",
"010 1234 5678",
"(010)1234.5678",
"1234-5678",
];
for (const p of inputs) {
console.log(p, "→", formatPhone(p));
}010-1234-5678 → 010-1234-5678 01012345678 → 010-1234-5678 010 1234 5678 → 010-1234-5678 (010)1234.5678 → 010-1234-5678 1234-5678 → null
3/5/2026 这样顺序不同的格式,年份的位置对不上,结果是 null。// 只检查月1~12、日1~31(拦不住2月30日)
function formatDate(s) {
const t = s.replace(/\s/g, "").replace(/\.$/, "");
const parts = t.split(/[.\/-]/).map(Number);
if (parts.length !== 3) return null;
const y = parts[0], m = parts[1], d = parts[2];
if (!Number.isInteger(y) || y < 1000) return null;
if (!(m >= 1 && m <= 12 && d >= 1 && d <= 31)) {
return null;
}
const mm = String(m).padStart(2, "0");
const dd = String(d).padStart(2, "0");
return y + "-" + mm + "-" + dd;
}
const dates = [
"2026.3.5.",
"2026/03/05",
" 2026-3-15 ",
"2026. 12. 1",
"2026.13.1",
"3/5/2026",
];
for (const x of dates) {
console.log(JSON.stringify(x), "→", formatDate(x));
}"2026.3.5." → 2026-03-05 "2026/03/05" → 2026-03-05 " 2026-3-15 " → 2026-03-15 "2026. 12. 1" → 2026-12-01 "2026.13.1" → null "3/5/2026" → null
6.保护原始数据,核对结果
自动化中最大的事故,是覆盖了原始数据而无法恢复。原则是:代码只读取原始数据,结果写到新变量或新文件里。如果处理的是文件,动手之前先另外复制一份原件,结果文件的名字里加上日期或“整理版”以示区别。第6讲看到过,map、filter 会生成新数组而不改动原数组,正好符合这个原则。
结果要从三个方面核对。第一是条数核对:打印原始记录数、结果记录数和被筛掉的条数,看加起来是否对得上。第二是抽样核对:从结果中挑几行,和原始数据手工比较,尤其要看第一行、最后一行和看着奇怪的行。第三是合计核对:看电子表格软件算出的合计和代码算出的合计是否一致。把整理前后的文本放进文本比较工具,就能一眼看出改动了哪些地方。
真实的 CSV 文件里,有时这一讲的简单 split(",") 会出错。如果某一格里有逗号,规则是把这一格用双引号括起来,于是写成 "铅笔, 蓝色" 的一格就会被切成两格。还有格内换行、引号里再套引号等规则。所以处理真正的文件时,要用电子表格软件打开处理,或者使用正确实现了 CSV 规则、经过验证的库(解析器)。这一讲的方法适合格式由自己定下的简单文本。
split 不懂引号规则。const line = '2026-03-03,"铅笔, 蓝色",2,500';
const cells = line.split(",");
console.log(cells.length);
console.log(cells);5 [ '2026-03-03', '"铅笔', ' 蓝色"', '2', '500' ]
\r\n。奇怪的行不要丢掉,而是归入“待定”,交给人来看。const text = "姓名,金额\r\n小明,3000\r\n\r\n小红,abc\r\n";
const lines = text.split(/\r?\n/).slice(1);
const ok = [];
const bad = [];
for (const line of lines) {
if (line.trim() === "") continue;
const c = line.split(",");
const n = Number(c[1]);
if (c.length === 2 && Number.isFinite(n)) {
ok.push({ name: c[0], amount: n });
} else {
bad.push(line);
}
}
console.log("已处理", ok.length, "待定", bad.length);
console.log(bad);已处理 1 待定 1 [ '小红,abc' ]
- 原始数据只读不改,结果写到新变量、新文件里
- 确认原始条数 = 已处理条数 + 待定条数
- 挑出第一行、最后一行和奇怪的行,手工比较
- 至少一个合计用另一种方法(计算器、电子表格软件)验算
- 真正的 CSV 文件用电子表格软件或经过验证的 CSV 解析器处理
7.和 AI 助手一起做自动化
自动化代码很适合交给 AI 编程助手来写。不过,想得到好结果,就要把输入的样子、想要的输出、例外的处理规则说清楚。比起“帮我整理一下 CSV”,“写一个函数,从表头为‘部门,金额’的文本中返回各部门合计的对象,金额不是数字的行另外收集起来”能得到准确得多的代码。
给 AI 看数据时,不要粘贴真实的客户名单和联系方式,只给几行格式相同的假数据。只要知道格式,就能写出代码。拿到代码后,在自己的电脑上用真实数据运行,并同样做上面的条数、抽样和合计核对。
怎样阅读和测试拿到的代码,下一讲第10讲会详细介绍。如果自己动手做过这一讲的练习题,就更容易发现 AI 写的代码里漏掉了 Number 转换,或者没有处理空行的地方。
📌 要点总结
- 经常重复、规则明确、数量大、容易出错的工作是自动化的候选
- 用
split("\n")分行,用split(",")分列,数字列用Number转换 - 分组合计是在空对象里用
(合计[键] || 0) + 值累加 - 不符合整理规则的值不要硬改,标记为
null或待定 - 保护原始数据,用条数、抽样和合计核对结果,真正的 CSV 文件用专门的解析器或电子表格软件处理
🧪 代码练习
用虚构的记录做几个小小的自动化函数。测试里也包含空数据、格式错误的值等边界情况。
代码只在本浏览器内的隔离环境中运行,不会发送到服务器。无法访问网络,超过2秒会自动停止。改过的代码只保存在此浏览器中。按 Ctrl+Enter(⌘+Enter)运行,Tab 键输入两个空格(先按 Esc 再按 Tab 可跳到下一项)。
浏览器脚本未开启,无法运行代码,但题目、初始代码、自动检查项和参考答案都可以直接阅读。
1求金额合计
请完成函数 totalAmount(text)。text 是一个字符串,第一行是表头 姓名,金额,从第二行起是一条条 姓名,金额 记录。请以数字形式返回所有金额之和。没有记录时返回0,空行跳过。
totalAmount("姓名,金额\n小明,3000\n小红,4500")期望值7500totalAmount("姓名,金额\n张伟,1200\n\n小明,800\n")期望值2000totalAmount("姓名,金额")期望值0totalAmount("姓名,金额\n小红,0\n张伟,250")期望值250
💡 提示
c[1] 是字符串。用 Number(c[1]) 转换后再相加;如果 line.trim() === "",就用 continue 跳过。
查看参考答案
function totalAmount(text) {
const lines = text.split("\n").slice(1);
let total = 0;
for (const line of lines) {
if (line.trim() === "") continue;
const c = line.split(",");
total += Number(c[1]);
}
return total;
}2整理名单
请完成函数 cleanNames(list)。它接收一个字符串数组,去掉每个名字的首尾空格,把连续的空格缩成一个,然后去掉空名字和重复项,返回结果数组。顺序保持第一次出现的顺序。
cleanNames([" 小明 ","小红","小明"])期望值["小明","小红"]cleanNames(["李 强","李 强 ",""])期望值["李 强"]cleanNames([])期望值[]cleanNames([" ","","小丽"])期望值["小丽"]cleanNames(["小红","小明","小红","小明"])期望值["小红","小明"]
💡 提示
用 replace(/\s+/g, " ") 缩减空格,用 filter 去掉空字符串,再用 [...new Set(数组)] 去重。
查看参考答案
function cleanNames(list) {
const cleaned = list
.map((s) => s.trim().replace(/\s+/g, " "))
.filter((s) => s !== "");
return [...new Set(cleaned)];
}3统一电话号码格式
请完成函数 formatPhone(s)。如果字符串只保留数字后是11位并且以 01 开头,就返回 010-1234-5678 这样的样子(3-4-4),否则返回 null。
formatPhone("01012345678")期望值"010-1234-5678"formatPhone("010 9876 5432")期望值"010-9876-5432"formatPhone("(010)1111.2222")期望值"010-1111-2222"formatPhone("1234-5678")期望值nullformatPhone("")期望值nullformatPhone("02-123-45678")期望值null
💡 提示
s.replace(/\D/g, "") 只保留数字。如果 d.length !== 11 || !d.startsWith("01"),就返回 null。
查看参考答案
function formatPhone(s) {
const d = s.replace(/\D/g, "");
if (d.length !== 11 || !d.startsWith("01")) {
return null;
}
return d.slice(0, 3) + "-" + d.slice(3, 7) +
"-" + d.slice(7);
}4分组合计
请完成函数 sumByGroup(text)。text 是第一行为表头 部门,金额 的多行字符串。请返回一个对象,以部门名为键、该部门金额之和为值。没有记录时返回空对象 {},空行跳过。
sumByGroup("部门,金额\n销售,100\n研发,200\n销售,50")期望值{"研发":200,"销售":150}sumByGroup("部门,金额")期望值{}sumByGroup("部门,金额\n行政,0\n\n行政,30\n")期望值{"行政":30}sumByGroup("部门,金额\n研发,10\n研发,20\n研发,30")期望值{"研发":60}
💡 提示
现在的代码在同一部门再次出现时会覆盖之前的值。改成 (sums[c[0]] || 0) + Number(c[1]) 累加。
查看参考答案
function sumByGroup(text) {
const sums = {};
const lines = text.split("\n").slice(1);
for (const line of lines) {
if (line.trim() === "") continue;
const c = line.split(",");
sums[c[0]] = (sums[c[0]] || 0) + Number(c[1]);
}
return sums;
}🤖 可以这样问 AI
复制后把 [ ] 部分换成你的情况。不要直接相信回答,请与正文对照核实。
想把重复工作写成代码时
我每周都要手工做[工作说明]。输入的样子和下面的假示例一样,希望输出是[想要的结果样子]。请用 JavaScript 函数来写,并在注释里说明要转成数字的列、空行和格式错误的行是怎么处理的。格式错误的行不要丢掉,另外收集起来。 [假示例3~5行]
想请人检查自己写的自动化代码时
下面是我整理类似 CSV 文本的 JavaScript 代码。请指出有没有改动原始数据的地方、有没有忘记把字符串转成数字的地方,以及遇到格内逗号、空行、行尾 `\r` 等情况时会怎样,再帮我设计5个用来检查的测试输入。 [我的代码]
想制定整理规则时
[电话号码/日期/姓名]这一列,每个人录入的样子都不一样。请看下面的假示例,提出一套统一的规则,并把不符合规则、需要人来确认的情况整理成列表。 [假示例]
🧰 相关工具
可以当场检查、整理本讲涉及的 JSON、正则表达式和文本的工具。请不要粘贴真实的个人信息或密码。
- MDN Web Docs — JavaScript 指南(Guide)以及 String、Array、Set、Object.entries 参考文档
- RFC 4180 — 关于 CSV 格式(逗号、双引号、换行规则)的一般说明
达成全部学习目标后,请点击完成。
此浏览器无法保存记录,仅在当前页面显示。