选择分支
选择分支(alternation)是正则里的"或"运算——用竖线分隔几个候选,匹配其中任意一个。看似简单,但它优先级最低这个特性,让新手经常踩"作用范围太大"的坑。这一章把这个看似简单的符号讲透。
基本用法
竖线把模式分成几个候选,引擎按从左到右的顺序尝试每个分支,第一个匹配成功的就采用。它的语法极其简单,但作用范围需要特别注意。
// 选择分支(alternation):用 | 表示"或"
cat|dog // 匹配 cat 或 dog
cat|dog|bird // 匹配 cat 或 dog 或 bird(三选一)
red|blue|green // 颜色枚举
// 注意优先级:| 的优先级是正则里最低的
// 下面两条完全等价(都是整个左右两边):
abc|def // 匹配 "abc" 或 "def"
(abc)|(def) // 同上
// 实例
/cat|dog/.test("I have a cat"); // true
/cat|dog/.test("I have a dog"); // true
/cat|dog/.test("I have a bird"); // false
// 多选项的"前缀共享"用分组更简洁
/(cat|dog|bird)s?/.test("cats"); // true (复数也匹配)关键认知:竖线的优先级是整个正则里最低的。所以 abc|def 表示"匹配 abc 或匹配 def",而不是"匹配 ab 加 c 或 d 加 ef"。要改变这个默认范围,必须用圆括号。
用圆括号限定范围
这是新手最容易出错的地方。没有圆括号时,竖线会一路吃到模式两端——你以为限定在某个位置,实际却覆盖了整个模式。下面这个对比必须看懂:
// 用圆括号限定 | 的范围(高频技巧)
// 错误写法:| 的范围太大,匹配了 "abc" 或 "xdef"
/^abc|xdef$/.test("abc"); // true (匹配 abc)
/^abc|xdef$/.test("xdef"); // true (匹配 xdef)
/^abc|xdef$/.test("abcx"); // true (! abc 开头就够,加了 x 也匹配)
// 正确写法:用括号把选择限定在中间
/^ab(c|x)def$/.test("abcdef"); // true
/^ab(c|x)def$/.test("abxdef"); // true
/^ab(c|x)def$/.test("abcxdef"); // false (不再误匹配)
// 经典场景:协议匹配 http 或 https
/^https?:\/\//.test("http://a.com"); // true (? 是更简洁的写法)
/^(https?|ftp):\/\//.test("ftp://a.com"); // true (用 | 列举多种)
// 多个长前缀用 | 配合分组
/^(Mr|Mrs|Ms|Dr)\.\s+\w+/.test("Dr. Smith"); // true
/^(!=|==|<=|>=)$/.test("=="); // true (运算符枚举)实用原则:只要选择分支出现在模式中间(而非整个模式的左半边或右半边),就必须用圆括号限定。是否捕获看你需不需要——不需要就加问号冒号变成非捕获组。
顺序敏感性
选择是顺序敏感的——左边的分支先尝试,匹配成功后右边就不再尝试。这意味着如果短选项在左、长选项在右,长选项可能永远没机会被匹配。这是新手第二个高频坑。
// 选择是"顺序敏感"的——左边的分支先尝试
// 这意味着长选项要写在短选项前面
// 反例:短的在前,长的永远匹配不到
/^a|ab$/.exec("ab");
// ["a"] (匹配了左边的 a,右边的 ab 没机会)
// 正解:长的在前
/^ab|a$/.exec("ab"); // ["ab"]
// 经典案例:匹配 "January" 或 "Jan"
/^January|Jan$/.exec("January"); // ["January"] 但其实匹配到 "Jan" 部分!
// 问题:| 的范围是整个 (January) | (Jan$),左边不含 $
// 改进:用括号限定
/^(January|Jan)$/.exec("January"); // ["January"]原则:长的分支写在前面,短的写在后面。或者更稳妥的做法——用圆括号 + 锚点明确边界,让分支之间互不干扰。这一节理解后,你会避免大量"明明应该匹配却不匹配"的诡异 bug。
选择 vs 字符类
新手常困惑:什么时候用字符类 [abc],什么时候用选择分支 a|b|c?其实有清晰的判断标准——看每个候选是单字符还是多字符。
// 选择 vs 字符类:什么时候用哪个
// 1. 单字符的"或" → 用字符类(更简洁)
[a-c] // a 或 b 或 c(等价于 a|b|c,但更短)
[aeiou] // 元音字母
// 2. 多字符的"或" → 只能用选择分支
cat|dog // 字符类无法表达
\d{3}|[A-Z]{2} // 三位数字 或 两位大写字母
// 3. 性能差异:字符类通常比选择快
// 字符类是"一次比较",选择是"先试左边,失败再试右边"
// 实例对比
// 匹配 "color" 或 "colour"
/colou?r/.test("color"); // true (? 优于 |)
/color|colour/.test("colour"); // true (但啰嗦)
// 匹配任意一位数字或字母
/[0-9a-zA-Z]/ // 字符类比 /\d|[a-zA-Z]/ 更快更清晰
// 但匹配 "fi" "km" "mi"(英里单位缩写)只能用 |
/fi|km|mi/- 单字符的或:优先用字符类。更短、更快、更清晰。
- 多字符的或:只能用选择分支,字符类表达不了。
- 性能:字符类是"一次比较",选择是"逐个尝试",前者通常更快。
实战组合
选择分支在实际项目里几乎都是配合分组、锚点、单词边界一起用。看几个真实场景:
// 实战:枚举常见值
// 1. 一周七天
/^\b(Mon|Tues?|Wed(nes)?|Thurs?|Fri|Sat|Sun)day\b$/i
// 2. 常见 HTTP 方法
/^(GET|POST|PUT|DELETE|PATCH|HEAD|OPTIONS)$/
// 3. 中国省份(简化)
/^北京|上海|广东|浙江$/
// 4. 文件扩展名(用分组限定)
/^\w+\.(jpg|jpeg|png|gif|webp)$/i.test("photo.jpg"); // true
// 5. 关键字过滤(避免与子串误匹配)
/\b(spam|ads|scam)\b/i.test("this is spam"); // true注意枚举类场景(星期、HTTP 方法、文件扩展名)必须配合单词边界或锚点,否则会匹配到子串——比如不加边界,Mon 会连 Monday 里的 Mon 也匹配,但你也匹配不到 Monster 里的 Mon(如果那不是你想要的)。
小结
- 竖线是"或",但优先级最低,默认作用到整个模式两端。
- 用圆括号限定范围,不需要捕获就加问号冒号。
- 顺序敏感,长分支写在前面。
- 单字符用字符类,多字符才用选择分支。
下一章讲正则的"高级技巧"——零宽断言(环视),让你"看一下但不消费字符"。
← 上一篇 分组与捕获
下一篇 零宽断言(环视) →