
我平时在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对象自带了很多方法其中match、search、replace、split是处理表格数据时出现频率最高的四个它们的共同点是都能接收正则表达式作为参数区别在于用途和返回结果。有一个朋友问过我这四个函数是不是功能重叠其实一点不重叠定位区别很大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很少单独使用更多是配合substring或slice去截取某个关键位置后的内容。比如从备注栏“备注家里没人放驿站”中截取“备注”之后的说明文字写法就是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把手机号和日期从原文本中移除目的是让后续的拆分更干净。这里有一个前提phone和dateStr本身是精确匹配到的子字符串用来做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/12或2024-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]拿到的就不是城市而是“广州市天河区体育西路”。如果字段结构不固定固定下标取值就不可靠了。我的建议是尽量先提取那些格式最稳定的信息比如手机号、日期、邮箱、订单号这些是“硬指标”。剩下无法规则匹配的字段再根据业务需要做关键词判断比如文本中是否含“市”“区”是否含“备注”“加急”等。具体判断可以借助search或indexOfif (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}再手动判断它前后字符是否是数字必要时用search加substring截取上下文判断。类似这种“正则读起来没问题但结果跑偏”的情况我一般建议先用一小段样本数据调试把匹配结果打印在弹窗或写到一个空白单元格里检查不要直接全表跑。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复制来的数据这类隐藏字符非常普遍统一替换后再做search和match会顺畅很多。4.4 split出来的数组里全是空字符串原因基本是两个一是分隔符正则写得太宽比如/[\s,]/本意是好的但数据里确实出现了连续分隔符能解决二是分隔符写得太窄比如实际数据里分隔符是“”两个全角逗号但正则里写的是/[,]/它只匹配一个逗号两个逗号之间就会留出一个空项。解决办法是在字符集后面加让连续多个分隔符被视为一个整体。遇到空项依然很多时还可以在拆分结果后面链式调用.filter(function(s){ return s.length 0; })把空字符串过滤掉这是成本最低的兜底方案。4.5 JS宏环境的一些运行限制WPS JS宏运行在WPS内置的JavaScript运行时里不是浏览器环境也不是Node.js环境所以一些Web API和Node模块都用不了。典型的是fetch、XMLHttpRequest这类网络请求接口在宏里直接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宏环境里是支持的放心用。