正则修饰符详解:i、g、m、s、u、y 和 d
每个 JavaScript 正则修饰符会改变什么,附可交互示例:忽略大小写 i、全局 g(以及 lastIndex 陷阱)、多行 m、dotAll s、Unicode u、粘性 y 和索引 d,还有 Python 中对应的写法。

· 阅读约 2 分钟
/^colou?r$/i
抓住
- color
- Colour
- COLOR
- colour
放过
- colr
- colours
- discolor
- kolor
修饰符会改变整个正则的行为。在 JavaScript 中,修饰符写在结尾的斜杠后面:/cat/gi。在 Python 中,它们是参数,比如 re.IGNORECASE,或者以内联形式写在正则开头,比如 (?i)。
JavaScript 共有七个修饰符。其中两个你会经常用到,有几个偶尔才需要,还有一个藏着几乎坑过所有人的陷阱。每个示例都可以实时编辑,修改正则就能看到各行的变化。
01i:忽略大小写
字母匹配时不区分大小写:
它省得你写 [Cc][Oo][Ll]…,而且在字符类里同样生效:加上 i 后,[a-z] 也接受大写字母。
Python:re.IGNORECASE、re.I 或 (?i)。
02g:全局
没有 g 时,正则找到第一个匹配就停下。加上它,match、matchAll 和 replace 等方法会作用于每一个匹配:
"a1b22c333".match(/\d+/); // ["1"], plus index and input
"a1b22c333".match(/\d+/g); // ["1", "22", "333"]
"a-b-c".replace(/-/, "+"); // "a+b-c"
"a-b-c".replace(/-/g, "+"); // "a+b+c"
Python 没有 g,而是通过选用不同的函数来实现:re.search 只找一个匹配,re.findall 和 re.finditer 找出全部匹配,而 re.sub 默认替换全部。
lastIndex 陷阱
带 g(或 y)的正则会在 lastIndex 中记住上一次匹配结束的位置,下一次 test() 就从那里开始。用同一个这样的正则去测试多个字符串,结果就会开始交替变化:
const hasDigit = /\d/g;
hasDigit.test("a1"); // true, lastIndex is now 2
hasDigit.test("b2"); // false: the search starts at index 2, past the "2"
hasDigit.test("b2"); // true again, lastIndex had reset to 0
解决方法很简单:只用于 test() 的正则不要加 g。检查是否匹配并不需要它。
03m:多行
^ 和 $ 匹配每一行的开头和结尾,而不是整段文本的开头和结尾。锚点与多行模式一文有完整介绍。
"one\ntwo".match(/^\w+$/gm); // ["one", "two"]
Python:re.MULTILINE、re.M 或 (?m)。
04s:dotAll
通常 . 匹配除换行符以外的任意字符。加上 s 后,它也能匹配换行符:
/start.*end/.test("start\nend"); // false
/start.*end/s.test("start\nend"); // true
在 s 出现之前,人们用 [\s\S] 表示“任意字符”,在旧代码里你仍然会看到它。
Python:re.DOTALL、re.S 或 (?s)。
05u:Unicode
u 让正则以完整的 Unicode 字符为单位工作,而不是 UTF-16 片段,并且会启用 \p{L}(任何文字中的任意字母)这样的属性转义:
它还会让 . 匹配一整个表情符号,而不是半个;并让解析器变得严格:在字符类外写 \- 这样不必要的转义会变成语法错误,而不是被悄悄忽略。JavaScript 较新的 v 修饰符是 u 的更严格的超集,支持在字符类内进行集合运算。
Python 的 re 默认支持 Unicode,但没有 \p{…}。第三方的 regex 模块提供了它。
06y:粘性
加上 y 后,匹配必须恰好从 lastIndex 开始,而不是从它之后的任意位置开始。它主要用于逐段遍历字符串的分词器:
const word = /\w+/y;
word.lastIndex = 0;
word.exec("hi there"); // ["hi"], lastIndex is now 2
word.exec("hi there"); // null: index 2 is a space, and y won't skip ahead
07d:索引
d 会给每个匹配加上一个 indices 数组,包含整个匹配以及每个分组的起止位置:
const m = /(\d+)-(\d+)/d.exec("pages 12-30");
m.indices[0]; // [6, 11], the whole match
m.indices[1]; // [6, 8], the first group, "12"
在编辑器中高亮匹配时很方便。Python 内置了这个功能:m.span(1)。
08试一试
用 i 修饰符让这个正则保持简短:
抓住任意大小写的 yes,其他都不要。
目标 · 抓住 0/5
- yes目标
- Yes目标
- YES目标
- yEs目标
- yeS目标
无辜词 · 误抓 0
- yesterday无辜
- eyes无辜
- no无辜
- ye无辜
- y e s无辜
输入正则——棋盘会实时更新。