WPS JS宏正则表达式实战:四大字符串函数高效处理表格数据
2026/9/9 17:12:05 网站建设 项目流程

我平时在WPS表格里处理数据,最烦的就是碰到那种“一列数据里什么都有”的情况,比如姓名、手机号、备注全塞在一个单元格里,或者从系统导出的报表带着一堆多余字符。以前用VBA写正则还得专门开启引用,麻烦;后来切到WPS JS宏,发现JavaScript本身就内置了正则支持,直接写/正则/就能用,再配合match、search、replace、split这几个字符串函数,清洗数据、提取信息、拆分文本的效率直接翻倍。这篇我就把这几件事一次讲透:WPS JS宏里正则表达式怎么用、这四个函数各自是什么脾气、怎么组合出手解决实际问题,以及我踩过的一些坑。适合正在用WPS做表格整理、刚从VBA转JS宏的办公玩家,也适合想把手动整理数据变成一键批量处理的新手参考。

1. WPS JS宏的基础认知:为什么字符串处理一定要学会它

1.1 从VBA到JS宏:不是替代,而是多了一把顺手的刀

WPS宏目前有两套脚本体系,一套是我们熟悉的VBA宏,另一套就是以JavaScript为核心的JS宏。VBA的生态成熟,网上代码一大把,但它在WPS里并非全功能开放,尤其涉及正则表达式时,VBA要么用VBScript.RegExp这个外部组件,要么得手动开启引用,不够顺手。JS宏则不一样,它运行在WPS内置的JavaScript运行时里,语法天然支持正则表达式,不需要引第三方库,写/abc/就是正则,写"abc".match(/a/)就能匹配,这对从编程背景转过来的人特别友好,对没写过代码的人而言,JS的语法也相对直观好读。

我推荐所有常折腾WPS表格的人都把JS宏捡起来,至少做到“能看懂、能改、能拼”。今天要讲的这四个字符串函数,几乎就是处理表格文本数据的入门必选项。它们虽然也能用普通字符串参数调用,但只有在配上正则表达式时,才真正体现威力。

1.2 正则表达式在JS宏里的写法与基本原理

正则表达式,通俗说就是“按规则找文本”。你可以把它想象成在文本里搜索时用的一套特殊通配符系统,比Excel通配符*?强得多。在JS宏里,正则表达式有两种写法:字面量写法/规则/修饰符,以及构造方式new RegExp("规则", "修饰符")。日常用字面量写法就够,里面反斜杠不用转义,看着清爽。

几个最常用的元字符,我直接用自己的话解释:

  • \d匹配一个数字,等价于[0-9]\D匹配非数字。
  • \w匹配字母、数字、下划线;\W匹配这三类之外的字符。
  • \s匹配空格、制表符、换行等空白字符;\S匹配非空白。
  • .匹配除换行外的任意字符。
  • []是一个字符集合,[a-zA-Z]匹配任意字母;[0-9]匹配任意数字。
  • ()是分组,也是捕获组,后面结合replace时特别好用。
  • {n}表示重复n次,{n,}表示至少n次,{n,m}表示n到m次。
  • *表示前面元素出现0次或多次,+表示至少1次,?表示0次或1次。
  • ^匹配开头,$匹配结尾。

修饰符也有几个常用的:g是全局匹配,不加它时只匹配第一处,加了它则匹配所有;i是忽略大小写;m是多行模式。这些内容看起来多,但实际用的时候只需要记住最核心的几十个组合,边做边查也行。我自己最常写的正则也就是手机号1[3-9]\d{9}、日期\d{4}-\d{1,2}-\d{1,2}这类,难度不大,但效率极高。

1.3 四个字符串函数在JS宏中的定位

JavaScript的String对象自带了很多方法,其中matchsearchreplacesplit是处理表格数据时出现频率最高的四个,它们的共同点是都能接收正则表达式作为参数,区别在于用途和返回结果。有一个朋友问过我,这四个函数是不是功能重叠?其实一点不重叠,定位区别很大:

  • match是“找出所有匹配的内容”,返回数组,适合做数据提取。
  • search是“找到匹配内容的位置”,返回索引数字,适合做存在性判断和定位。
  • replace是“把匹配的内容替换成别的”,返回新字符串,适合做清洗和脱敏。
  • split是“按匹配的内容把字符串切开”,返回数组,适合做文本拆分。

这四个函数和正则一配合,基本能覆盖表格里八成以上的文本处理场景。接下来我一个一个拆开讲,每节都会有能直接抄的代码例子。

2. match、search、replace、split逐个拆解:用法、坑与实战示例

2.1 match:一次性提取所有符合条件的文本

match的语法是字符串.match(正则)。如果正则表达式里没有g修饰符,它返回的数组第一项是第一个完整匹配,后续项是捕获组的内容;如果加了g,它返回的数组是所有完整匹配结果,不再包含捕获组。这是初学者最容易懵的地方,后面我会专门讲。

举一个最贴近办公的场景:某列数据是“订单号20240512001,金额899,备注加急”,你现在要把所有数字提取出来。直接写:

var str = "订单号20240512001,金额899,备注加急"; var nums = str.match(/\d+/g); // 返回 ["20240512001", "899"]

注意\d+里的+,它表示连续的数字串,若无脑只写\d,返回的会是每一位数字,分别是"2","0","2","4"这样,完全不对。所以提取连续数字串时,+是必须的。

再比如从混合文本中提取邮箱,可以用/\w+@\w+\.\w+/g。这里要提醒一个容易犯的错:正则里的.没有加反斜杠的话,它匹配的是任意字符,而邮箱地址里我们想要的是字面上的点,所以必须写成\.

接下来是match的常见坑:如果正则没加g,返回值中可能包含多余的分组信息,取数时要小心索引。另外,如果没有任何匹配,match返回的不是空数组,而是null。这在后续处理中意味着你不能直接调用数组方法,必须先做判断。我在写宏的时候已经习惯这样写:

var phone = raw.match(/1[3-9]\d{9}/); if (phone) { // 有匹配时再处理phone[0] }

这个习惯帮我避免了很多运行时错误。

2.2 search:精准定位“匹配内容在哪里”

search的语法是字符串.search(正则),返回第一个匹配位置的起始索引,从0开始;找不到时返回-1。它的作用和indexOf很像,但indexOf只能按普通字符串精确查找,search能用正则做模糊查找,灵活性高出一截。

说一个实际场景:单元格内容是“张三,北京,有意向,号码13812345678”,你需要在整段文本里找到手机号出现的位置,再取它后面的内容。其实用search可以这样写:

var str = "张三,北京,有意向,号码13812345678"; var pos = str.search(/1[3-9]\d{9}/); // pos的值为10,因为“号码”占了两个字,从索引10开始就是手机号 if (pos >= 0) { var tail = str.substring(pos); // 得到 "13812345678",如果后面还有内容,也能一并截取 }

在办公自动化里,search很少单独使用,更多是配合substringslice去截取某个关键位置后的内容。比如从备注栏“备注:家里没人,放驿站”中截取“备注”之后的说明文字,写法就是:

var idx = raw.search(/备注/); if (idx >= 0) { var note = raw.substring(idx + 2); }

这里idx + 2是因为“备注”这个词占了两个字符,如果不想硬算长度,更专业的写法是用match拿到“备注”这个词再取它的length。不过实际写多了你会发现,search配合具体索引其实直白好用,只要确认文本结构稳定就行。

2.3 replace:清洗、脱敏、重排格式一招搞定

replace的语法是字符串.replace(正则, 替换内容),返回新字符串,不会修改原字符串。如果不加g修饰符,它只替换第一个匹配项;加了g才替换所有。这是大多数新手第一次用replace时的最大翻车点:写了replace(/\d/, "")想着把数字全删掉,结果只删了第一个数字,剩下的还在。

一个典型的清洗场景:清理导出数据里混杂的“特殊符号+空格+制表符”,可以用:

var dirty = "姓名:张三\t电话:13812345678 备注:加急"; var clean = dirty.replace(/[::\t ]+/g, ","); // 结果变成 "姓名,张三,电话,13812345678,备注,加急"

注意我用了[::\t ]+,意思是将全角冒号、半角冒号、制表符、空格这些字符的连续重复全部合并替换为一个“,”。这种写法比连续写多个replace干净很多,正则表达式就是用来做这种批量合并的。

另一个高光用法是结合捕获组做内容重排。比如把“2024/05/12”改成“2024年05月12日”,直接写:

var date = "2024/05/12"; var newDate = date.replace(/(\d{4})\/(\d{1,2})\/(\d{1,2})/, "$1年$2月$3日"); // 结果是 "2024年05月12日"

这里用括号把年、月、日分别捕获,替换内容里的$1$2$3分别对应第一个、第二个、第三个捕获组。手机号脱敏也是这个路子:

var phone = "13812345678"; var masked = phone.replace(/^(\d{3})\d{4}(\d{4})$/, "$1****$2"); // 结果是 "138****5678"

我目前在做客户信息表时,脱敏基本都用这一行代码解决,速度快、规则清楚。另外提醒一句,replace的第二个参数除了字符串,也可以传入一个函数,这个函数能针对每个匹配内容做更复杂的计算,适合替换规则不统一的情况,后面案例里我会展示。

2.4 split:把混合文本按规则拆成多列

split的语法是字符串.split(正则或字符串),作用就是把原字符串按分隔符切成数组。它本来用字符串参数也能切,但配合正则后,最大的好处是能“多分隔符统一拆分”。

举个例子,某列数据是“张三,北京|13812345678 备注:家里没人”,现在想按逗号、竖线、空格、全角冒号把所有字段切出来,如果用split(",")只能切逗号,剩下的竖线和空格还得再处理。正则一句话搞定:

var str = "张三,北京|13812345678 备注:家里没人"; var parts = str.split(/[,|:\s]+/); // 返回 ["张三", "北京", "13812345678", "备注", "家里没人"]

[,|:\s]+表示“逗号、竖线、全角冒号、空白字符”中的任意一个,并且连续出现多个也按一个分隔符处理。这个+非常重要,不然遇到连续分隔符的时候,数组中间会出现一堆空字符串,等于拆完还得二次清洗。

拆分后配合数组下标,就能把内容填进不同列。但实际数据往往没有这么规整,可能存在缺失字段,比如有人没有备注,有人地址里又带着空格,这种时候直接按固定下标取值就会取错。稳妥的做法是先把结果过滤掉空项,再依据每一部分的关键特征二次判断它是什么字段。这部分内容我在第3节的完整案例里会展示写法。

2.5 四个函数的选型对比

有时候拿到需求会犹豫该用哪个函数,这里我列一个自己的选型习惯:

需求首选函数原因
提取手机号、日期等关键信息match直接返回匹配结果,无需再截取
判断某类文本是否存在并获取位置search返回位置索引,找不到返回-1,判断直观
清除多余字符、脱敏、统一格式replace支持捕获组与回调,替换能力最强
按多种分隔符拆字段split配合正则可以多分隔符一次切干净
既要判断存在又要提取内容search + substring先定位再截取,避免match返回null时报错

这些函数的返回值类型不同,实践中最稳妥的组合思路是“先search判断,再match或substring提取,最后replace清洗”。接下来我直接用一个完整案例把这套思路串起来。

3. 实操案例:从混合文本里一键提取姓名、手机号、城市和日期

3.1 需求背景与数据格式

假设你的表格A列存的是系统导出的混合文本,每行格式类似下面这样:

张三-13812345678-北京-2024/05/12-备注加急 李四|13998765432|上海|2024-06-01 王五,13611112222,广州,2024-07-15

可以看到分隔符不统一,有短横线、竖线、逗号,日期格式也不统一,有斜杠也有横杠,最后还可能有备注。现在需要把姓名、手机号、城市、日期分别提取到B、C、D、E列。我选择用JS宏批量处理,一次性跑完整个工作表。

3.2 完整宏代码与逐段讲解

打开WPS表格后,按开发工具下的“查看代码”进入JS宏编辑器,新建一个模块,把下面代码粘贴进去即可:

function ExtractInfo() { var sheet = ActiveSheet; var row = 2; // 数据从第2行开始,第1行是标题 while (true) { var raw = sheet.Range("A" + row).Text; if (!raw) break; // A列单元格为空,停止循环 var name = ""; var phone = ""; var city = ""; var dateStr = ""; // 1. 提取手机号:1开头,第二位3-9,后面9位数字 var phoneMatch = raw.match(/1[3-9]\d{9}/); if (phoneMatch) phone = phoneMatch[0]; // 2. 提取日期:兼容 2024/05/12 和 2024-06-01 var dateMatch = raw.match(/\d{4}[\/\-]\d{1,2}[\/\-]\d{1,2}/); if (dateMatch) dateStr = dateMatch[0]; // 3. 把已经提取的手机号和日期从原文本中移除 var rest = raw.replace(phone, "").replace(dateStr, ""); // 4. 用正则拆分剩余字段,过滤空项 var parts = rest.split(/[-—–|,,、\s]+/).filter(function(s) { return s.length > 0; }); if (parts.length > 0) name = parts[0]; if (parts.length > 1) city = parts[1]; // 5. 最后一项可能是备注,但这里没要求提取,保留即可 // 6. 写入结果 sheet.Range("B" + row).Value2 = name; sheet.Range("C" + row).Value2 = phone; sheet.Range("D" + row).Value2 = city; sheet.Range("E" + row).Value2 = dateStr; row = row + 1; } }

这段代码里有个细节值得单独说明。第3步用replace把手机号和日期从原文本中移除,目的是让后续的拆分更干净。这里有一个前提:phonedateStr本身是精确匹配到的子字符串,用来做replace时,即便不带g修饰符,也能准确替换掉刚刚匹配到的那个文本。不过如果同一文本里手机号和日期内容重复出现,比如日期出现了两次,replace就只会替换第一处。所以更稳妥的写法是用正则替换:

var rest = raw.replace(/1[3-9]\d{9}/, "").replace(/\d{4}[\/\-]\d{1,2}[\/\-]\d{1,2}/, "");

这样逻辑更自洽,省得依赖已经匹配到的变量。我建议你实际使用时采用这种写法。

拆分正则我用了[-—–|,,、\s]+,其中短横线包含了三种:半角-、全角、以及,如果数据中还出现了其他类型的连字符,可以继续往字符集里加。之所以不直接用-写在外面,是因为在正则字符集内部,短横线放在开头或结尾不会被当作范围连接符,否则像[a-z]那样是有特殊含义的。这是初学者很容易踩的坑,我放在下一节重点讲。

3.3 日期的二次清洗

上面的代码已经能把日期提取出来,但是不同行可能提取出2024/05/122024-06-01这种不同格式。如果希望统一成2024年05月12日的展示风格,可以在写入前加一行:

dateStr = dateStr.replace(/(\d{4})[\/\-](\d{1,2})[\/\-](\d{1,2})/, "$1年$2月$3日");

这行replace把斜杠或短横线统一替换成“年月日”。值得注意的是,我并没有对月份和日期的前导零做补齐,也就是2024/5/2会变成2024年5月2日,而不是2024年05月02日。如果需要补齐,可以写一个小的格式化函数:

function pad2(s) { s = String(s); return s.length < 2 ? "0" + s : s; }

然后在替换回调里调用:

dateStr = dateStr.replace(/(\d{4})[\/\-](\d{1,2})[\/\-](\d{1,2})/, function(_, y, m, d) { return y + "年" + pad2(m) + "月" + pad2(d) + "日"; });

这里利用了replace的第二个参数为函数的能力,函数收到的参数依次是:整个完整匹配、第一个捕获组、第二个捕获组……以此类推。这种写法在处理“规则需要计算”的替换时非常强大。

3.4 处理含备注和地址的复杂行

实际数据比示例更乱的情况非常常见。比如某行是“王五,13611112222,广州市天河区体育西路,2024-07-15,第一次咨询”,这时候按我之前拆分剩余字段的逻辑,parts[1]拿到的就不是城市,而是“广州市天河区体育西路”。如果字段结构不固定,固定下标取值就不可靠了。

我的建议是:尽量先提取那些格式最稳定的信息,比如手机号、日期、邮箱、订单号,这些是“硬指标”。剩下无法规则匹配的字段,再根据业务需要做关键词判断,比如文本中是否含“市”“区”,是否含“备注”“加急”等。具体判断可以借助searchindexOf

if (rest.search(/市/) >= 0) { // 说明剩余字段里大概率包含城市信息 }

这里不追求代码多炫,而是强调“数据处理前想清楚哪些字段是稳定的、哪些是易变的”。WPS宏做数据清洗,最重要的不是写多长的正则,而是对数据结构的理解。

4. 常见问题与排查技巧实录

4.1 正则写对了,match却返回一堆无关内容

很多时候问题出在正则的边界没有限定。比如提取手机号,如果你只写/\d{11}/,那么一个文本里有13位数字时,它会从第1位开始匹配11位,多出来的2位也会参与匹配,导致结果不对。正确写法是给手机号加边界,前后不要紧挨着数字:

raw.match(/(?<!\d)1[3-9]\d{9}(?!\d)/)

(?<!\d)是负向后行断言,表示“前面不是数字”;(?!\d)是负向前行断言,表示“后面不是数字”。这种写法的意思是:这一段11位数字必须独立存在,前后不能有别的数字挨着。不过JS宏环境对不同正则特性的支持程度我试下来不一致,所以更保险的替代方案是:先匹配\d{11},再手动判断它前后字符是否是数字,必要时用searchsubstring截取上下文判断。

类似这种“正则读起来没问题但结果跑偏”的情况,我一般建议先用一小段样本数据调试,把匹配结果打印在弹窗或写到一个空白单元格里检查,不要直接全表跑。

4.2 replace只替换了第一个,剩下的纹丝不动

这个坑出现频率极高。原因就是正则缺少g修饰符。判断一个替换需求是否需要“全部替换”,可以在写代码前先问自己:我要清除的是“所有”这类字符,还是“第一个”这类字符?前者必须加g,后者保持默认即可。比如删除所有空格,必须写成replace(/\s/g, ""),少一个g,结果天差地别。调试这类问题时,优先排查两个位置:修饰符有没有加,替换参数是不是字符串而不是正则。

4.3 单元格内容明明有中文,search却返回-1

这种情况多数不是正则写错了,而是文本中混入了不可见字符,比如全角空格、不换行空格、制表符等。肉眼看不到的东西最难排查,我一般先复制问题单元格内容,在宏里用charCodeAt查看每个字符的Unicode编码,确认看不见的字符到底是什么。比如全角空格的码点是12288,普通半角空格是32,制表符是9

更高效的办法是处理前先做一次“归一化”,把常见不可见字符统一替换掉:

raw = raw.replace(/[\u00a0\u200b\u3000\t]/g, " ");

这里\u00a0是不换行空格,\u200b是零宽空格,\u3000是全角空格。WPS表格从网页或PDF复制来的数据,这类隐藏字符非常普遍,统一替换后再做searchmatch会顺畅很多。

4.4 split出来的数组里全是空字符串

原因基本是两个:一是分隔符正则写得太宽,比如/[\s,]+/本意是好的,但数据里确实出现了连续分隔符,+能解决;二是分隔符写得太窄,比如实际数据里分隔符是“,,”(两个全角逗号),但正则里写的是/[,,]/,它只匹配一个逗号,两个逗号之间就会留出一个空项。解决办法是:在字符集后面加+,让连续多个分隔符被视为一个整体。遇到空项依然很多时,还可以在拆分结果后面链式调用.filter(function(s){ return s.length > 0; }),把空字符串过滤掉,这是成本最低的兜底方案。

4.5 JS宏环境的一些运行限制

WPS JS宏运行在WPS内置的JavaScript运行时里,不是浏览器环境,也不是Node.js环境,所以一些Web API和Node模块都用不了。典型的是fetchXMLHttpRequest这类网络请求接口,在宏里直接fetch会报“fetch not define”这类错误。这一点在搜索记录里很常见,很多人想在宏里发HTTP请求,结果发现根本不能直接调用。如果确实有联网需求,需要通过WPS提供的Http对象或调用其他可用的接口方式,具体以你当前WPS版本的API文档为准。我在写宏时尽量避免依赖运行环境之外的全局对象,老老实实处理表格数据,稳定性最高。

另外,JS宏在读取单元格时,Range("A1").Text拿到的是单元格显示出来的文本,Value2拿到的是底层值。如果单元格里是日期格式,Text可能显示成2024/05/12,而Value2可能是数字序列。所以做字符串正则处理时,优先用Text或先把Value2转成字符串,否则数字类型直接调用match会报错。

4.6 问题速查表

现象可能原因解决方案
match返回数字数组而不是字符串数组没加g修饰符,且正则中有捕获组加g,或按索引取第一项
match返回null导致后续报错无匹配项先判断if (result)再处理
replace只替换一个缺g修饰符replace(/正则/g, "")
search找不到中文文本含不可见字符先替换全角空格、零宽空格等
split结果含空字符串分隔符连续或多个不同分隔符字符集后加+,并用filter过滤空项
报错“fetch not define”JS宏环境不是浏览器,无fetch接口换用WPS提供的网络支持方式,或不用网络功能

5. 我给JS宏新手的三个实操建议

5.1 写好辅助函数,别在一行正则里硬刚

正则表达式本身已经够难读了,如果每个宏里都写一次超长正则,后期维护相当痛苦。我习惯把常用的匹配逻辑封装成小函数,比如“提取手机号”“提取日期”“清理空白字符”,然后所有宏复用它。这样既减少重复代码,又方便调试单个逻辑。

一个简单的封装思路:

function getPhone(str) { var m = String(str).match(/(?<!\d)1[3-9]\d{9}(?!\d)/); return m ? m[0] : ""; }

以后处理任何文本,只要调getPhone(raw)就行。哪天手机号规则变了,也只需要改一处。

5.2 大批量处理时,先读取到数组,再循环写回

如果你需要处理的行数有几千甚至上万行,一个格子一个格子地读写会很慢。更高效的做法是先把整列数据一次性读取到JavaScript数组里,在内存中完成所有字符串处理,最后再把结果一次性写回工作表。这样既快,也减少对表格的频繁操作带来卡顿。

例如:

var rangeData = sheet.Range("A2:A" + lastRow).Value2; var result = []; for (var i = 0; i < rangeData.length; i++) { var raw = String(rangeData[i]); // 处理raw,把结果push到result } sheet.Range("B2:E" + lastRow).Value2 = result;

这里Range.Value2返回的是一个二维数组,处理时注意下标从0开始。一次性写回也是一样的二维数组结构,行列要和目标区域对应上。

5.3 不要在生产数据上直接跑,先备份或用样本试跑

这是老生常谈但必须强调。正则表达式有个特点:在小样本上跑得飞起的规则,放到真实数据里可能因为一个特殊字符就全盘崩掉。我现在的习惯是,从原始数据里抽取10到20行有代表性的样本,粘贴到一个临时工作表里,先在这上面把宏跑通,确认结果无误后再切换到完整数据上执行。即使出了问题,至少原始数据没被动过,心里不慌。

另一个好习惯是,在宏的最开始加一句确认弹窗,比如:

if (!confirm("即将处理当前工作表,是否继续?")) return;

这个操作不复杂,但在手滑双击运行宏的时候,真的能阻止一次灾难。JS宏里可以用confirm弹窗,这个API在WPS宏环境里是支持的,放心用。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询