B 币安 · 全球最大的加密货币交易所币安 注册 → AD OKX OKX · 全球领先的加密货币交易所OKX 注册 → AD
💻 编程基础 · 第9讲 / 10

简单的自动化示例——用代码做表格计算和文本整理

把前面学过的值、条件、循环、函数、数组和字符串组合起来,做一个真正用得上的小自动化。把手工做的汇总和整理交给代码,同时养成保护原始数据、亲手核对结果的习惯。

⏱ 约22分钟 ✍️ 练习题 5 道 🧪 代码练习 4 题 更新 2026-10-09
🎯 本讲学习目标
  • 能说出判断一项重复工作是否值得写成代码的标准
  • 能把用逗号分隔的多行文本变成对象数组,并求出合计、平均值和分组合计
  • 能写出整理空格、空行和重复项,并把电话号码、日期统一成一种格式的函数
  • 能保留原始数据,并通过抽样手工核对来检验自动化的结果

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. 第1步 把文本分成行,跳过表头。
  2. 第2步 把每一行按逗号分开,取出部门和金额,金额用 Number 转换。代码里的 const [team, money] = … 是把分出来的数组的第一个、第二个值依次放进两个名字里的语法(解构赋值)。
  3. 第3步 在空对象里以部门名为键,把金额加上去。
  4. 第4步 用 Object.entries 把对象变成 [部门, 合计] 对组成的数组,遍历时记住最大的那个。
  5. 验算:销售是 32,000 + 18,000 = 50,000韩元,确认和代码的结果一致。
答案销售50,000韩元、研发41,000韩元、行政9,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 规则、经过验证的库(解析器)。这一讲的方法适合格式由自己定下的简单文本。

本该是4格,却被切成了5格。简单的 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 转换,或者没有处理空行的地方。

在自动化代码的最上面,用三行注释写下“输入的样子、输出的样子、处理不了的情况”。对几个月后的自己和 AI 助手来说,这都是最好的说明书。

📌 要点总结

  • 经常重复、规则明确、数量大、容易出错的工作是自动化的候选
  • 用 split("\n") 分行,用 split(",") 分列,数字列用 Number 转换
  • 分组合计是在空对象里用 (合计[键] || 0) + 值 累加
  • 不符合整理规则的值不要硬改,标记为 null 或待定
  • 保护原始数据,用条数、抽样和合计核对结果,真正的 CSV 文件用专门的解析器或电子表格软件处理

✍️ 练习题

请先自己作答,再展开“答案与解析”。

Q1. 把 "2026-03-01,铅笔,10,500".split(",") 分出的数量和单价直接用 c[2] + c[3] 相加,结果是什么?

⭕ 回答正确

❌ 再想一想

答案与解析
答案 ② "10500"

split 分出来的每一格都是字符串,所以 + 变成了拼接。要计算就必须用 Number 转换。

Q2. 下面哪项工作最值得自动化?

⭕ 回答正确

❌ 再想一想

答案与解析
答案 ② 从每周收到的几百行销售记录中算出各品类合计

这项工作经常重复、规则明确、数量大。润色语气或挑选场地都是需要判断的事。

Q3. 'a,"b, c",d'.split(",") 的结果有几格?

⭕ 回答正确

❌ 再想一想

答案与解析
答案 ③ 4格

简单的 split 不懂引号规则,连格内的逗号也会切开,所以是4格。真正的 CSV 要用专门的解析器或电子表格软件处理。

Q4. 电话号码整理函数收到了 "1234-5678" 这样不符合规则的值。最好的处理方式是什么?

⭕ 回答正确

❌ 再想一想

答案与解析
答案 ③ 返回 null 之类的标记,另外收集起来由人确认

猜测着修改不认识的值,错误数据就会悄悄混进去。处理不了的值要做好标记,由人来确认。

Q5. 请写出两种检验自动化结果的方法。

答案与解析
参考答案 条数核对:看原始条数是否等于已处理条数加待定条数;抽样核对:挑几行和原始数据手工比较(或者用另一种方法验算合计)。

把条数、抽样和合计核对结合起来,大多数选错列或漏行的错误都能发现。

🧪 代码练习

用虚构的记录做几个小小的自动化函数。测试里也包含空数据、格式错误的值等边界情况。

代码只在本浏览器内的隔离环境中运行,不会发送到服务器。无法访问网络,超过2秒会自动停止。改过的代码只保存在此浏览器中。按 Ctrl+Enter(⌘+Enter)运行,Tab 键输入两个空格(先按 Esc 再按 Tab 可跳到下一项)。

浏览器脚本未开启,无法运行代码,但题目、初始代码、自动检查项和参考答案都可以直接阅读。

1求金额合计

请完成函数 totalAmount(text)。text 是一个字符串,第一行是表头 姓名,金额,从第二行起是一条条 姓名,金额 记录。请以数字形式返回所有金额之和。没有记录时返回0,空行跳过。

自动检查
  • totalAmount("姓名,金额\n小明,3000\n小红,4500")期望值 7500
  • totalAmount("姓名,金额\n张伟,1200\n\n小明,800\n")期望值 2000
  • totalAmount("姓名,金额")期望值 0
  • totalAmount("姓名,金额\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")期望值 null
  • formatPhone("")期望值 null
  • formatPhone("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 格式(逗号、双引号、换行规则)的一般说明

达成全部学习目标后,请点击完成。

💻 编程基础

  1. 1什么是程序:把事情拆成顺序、条件和循环的思维
  2. 2值、变量与数据类型:给值贴上名字标签
  3. 3条件语句:根据情况走不同的路
  4. 4循环 — 让同一件事准确地重复多次
  5. 5函数 — 拆成一个个有名字的小机器
  6. 6数组与对象 — 装载和处理数据
  7. 7字符串与文本处理——截取、查找、替换文字
  8. 8调试与读懂错误——把红字变成线索
  9. 9简单的自动化示例——用代码做表格计算和文本整理
  10. 10阅读并检验 AI 写的代码——测试、安全、许可证与个人信息
📚 推荐阅读
📊Using AI for Spreadsheets, and Checking the Formulas→ 💻AI Coding Assistants: Security and Licence Risks→ 💬Practising Conversation in a Foreign Language with AI→ ⚙️Finding Work Tasks Worth Automating with AI→
← AI时代基础学力课程