先说结论:这次真机调试失败,是因为微信真机的 JavaScript 运行环境不支持 \p{Script=Han} 这种 Unicode 属性转义写法。
同一段代码在微信开发者工具里可以运行,TypeScript 和 Lint 也没有报错。换到真机后,正则一执行就崩了:
MiniProgramError
Invalid regular expression: /\p{Script=Han}/u: Invalid property name
SyntaxError: Invalid regular expression问题不在接口,也不在正则匹配结果。真机根本无法构造这个正则。
真机不认识这种写法
原代码只是想判断文本里有没有汉字:
const containsHan = (text: string) => /\p{Script=Han}/u.test(text)\p{...} 是 JavaScript 的 Unicode 属性转义。配合 u 标志后,可以根据字符的 Unicode 属性进行匹配。这里的 Script=Han 指 Han 书写系统。
这是一种标准语法,但标准里有,不等于每个小程序运行环境都已经实现。当前真机在创建正则时抛出 SyntaxError,程序还没走到 .test(text),更谈不上返回 true 或 false。
这也是排查时最容易看错的地方。它不是“没有匹配到汉字”,而是“这个运行环境不接受这种正则写法”。只要代码执行到这里,当前调用就会中断。
改成真机能够执行的字符区间
当前需求只是判断一条短文本中是否含有常用汉字,没有必要依赖 Unicode 属性转义。可以直接写明字符范围:
const containsHan = (text: string) =>
/[\u3400-\u4DBF\u4E00-\u9FFF]/.test(text)这两个范围分别是:
U+3400–U+4DBF:CJK Unified Ideographs Extension A;U+4E00–U+9FFF:CJK Unified Ideographs 主区段。
这个写法不依赖 \p{...},在这次出问题的真机环境中可以正常构造和执行。
它也不是 \p{Script=Han} 的完整替代。辅助平面中的 Extension B 及后续扩展、部分兼容表意文字并不在这两个范围内。用它判断普通中文提示没有问题;如果处理人名、古籍或完整 Unicode 文本,就要重新确定覆盖范围。
必须使用 \p{...} 时先检测
如果确实需要 Unicode 属性提供的完整语义,要先检测目标运行时是否支持。不要直接写正则字面量,可以用 RegExp 构造函数把编译放进 try...catch:
const hanPattern = (() => {
try {
return new RegExp("\\p{Script=Han}", "u")
} catch {
return /[\u3400-\u4DBF\u4E00-\u9FFF]/
}
})()
const containsHan = (text: string) => hanPattern.test(text)如果运行时支持,就使用 \p{Script=Han};不支持则退回明确字符范围。
不要把正则字面量直接放进 try。不支持该语法的引擎可能在脚本解析阶段就失败,代码还没有机会执行到 catch。字符串形式的 RegExp 构造函数会在运行时编译正则,这个异常才能被捕获。
为什么开发者工具没有暴露问题
微信开发者工具和真机不是同一个 JavaScript 运行环境。开发者工具能运行,只能说明它使用的引擎支持该语法,不能证明真机也支持。
TypeScript 主要检查类型,Lint 也不会替每一种目标引擎执行正则。于是这段代码可以一路通过静态检查,直到真机真正创建正则时才报错。
这次正则又刚好写在错误文本处理函数里。接口原始错误已经返回,但客户端在整理提示文本时执行了这个正则,新的 SyntaxError 盖住了原始错误,所以最初看起来像是请求层吞掉了异常。这是正则不兼容造成的后果,不是问题本身。
怎么排查同类写法
先搜索客户端源码里是否还有 Unicode 属性转义:
rg -n -F '\p{' src找到后要根据用途处理。只是判断常用中文,可以改成明确区间;确实需要 Unicode 属性,就加运行时能力检测。最后必须在真机上执行到对应分支,不能只看开发者工具是否报错。
这个坑真正值得记住的只有一点:写给微信小程序的正则,要按真机运行时支持的语法来写。