字符类

字符类(character class)是正则里最常用的砖块——用方括号定义一个"集合",告诉引擎"匹配这个集合里的任意一个字符"。这一章我们把自定义字符类范围写法预定义简写取反一次性讲透。

自定义字符类

用方括号把几个字符包起来,就构成一个字符类——它只匹配一个字符,但这个字符可以是方括号里列出的任意一个。在方括号开头加脱字符表示"取反",匹配"不在列表里的字符"。

// 字符类(character class):用方括号定义"匹配其中任意一个字符"

[abc]      // a 或 b 或 c(三选一)
[^abc]     // ^ 在开头表示"取反":不是 a、不是 b、不是 c
[a-z]      // 任意小写字母(a 到 z 闭区间)
[A-Z]      // 任意大写字母
[0-9]      // 任意数字
[a-zA-Z]   // 任意字母(大小写)
[a-zA-Z0-9_]  // 字母、数字、下划线(等价于预定义类 \w)

// 实例
/[aeiou]/.test("hello");        // true (匹配任意元音)
/[^0-9]/.test("12345");          // false (全是数字,没有非数字)
/[A-Z][a-z]+/.exec("Hello");     // ["Hello"] (大写开头的小写串)

记住:不管方括号里写多少字符,它始终只匹配一个位置上的字符。要匹配多个,得配合下一章讲的量词(星号、加号、问号)。

预定义字符类

有几组字符集合出现频率极高——数字、字母、空白——正则为它们准备了一两个字符的简写。这些"魔法字符"必须背下来,日常 90% 的正则都离不开它们。

// 预定义字符类(背下来,天天用):

\d    // 数字(digit)    等价于 [0-9]
\D    // 非数字           等价于 [^0-9]
\w    // 单词字符(word)  等价于 [a-zA-Z0-9_]
\W    // 非单词字符        等价于 [^a-zA-Z0-9_]
\s    // 空白(space)     包括空格、Tab、换行、回车、换页
\S    // 非空白
.     // 任意一个字符(除换行,加 s 标志后含换行)

// 大小写对应"是 / 否":大写就是小写的反义
// \d ↔ \D    \w ↔ \W    \s ↔ \S

// 实例
/\d+/.exec("订单号 12345");      // ["12345"]
/\w+/.exec("hello world");      // ["hello"]
/\s+/.exec("a   b");            // ["   "]
/[a-z]+\d+/i.exec("abc123");    // ["abc123"]

几个易混点:点号默认不匹配换行(加 s 标志才匹配);大小写对应"是 / 否"——大写就是小写的反义,理解这种对称性能加速记忆。还有几个特殊序列(单词边界、字符串开头)会在锚点一章讲。

范围写法的细节

方括号里用连字符表示范围,比如 a-z 表示从 a 到 z。但范围有几个细节新手容易踩坑:必须按 ASCII 码顺序、可以连写多个范围、还能跨字符类别。

// 范围(range)的注意事项

// 1. 范围按 ASCII 码顺序,不能反过来
/[a-z]/.test("m");    // true
/[z-a]/;              // ❌ 语法错误(起始 > 结束)

// 2. 多个范围可以连写
/[a-zA-Z0-9]/.test("X");   // true
/[0-9a-fA-F]/.test("F");   // true (十六进制字符)

// 3. 范围两端不限于字母数字
/[ !"#$%]/.test(" ");      // true (含空格、感叹号、井号等)
/[\x00-\x7F]/.test("A");  // true (整个 ASCII 范围)

// 4. 中文区间需要小心:UTF-16 码点连续才行
/[\u4e00-\u9fa5]/.test("中");   // true (常用 CJK 汉字范围)
/[\u4e00-\u9fa5]+/.exec("一段中文abc");  // ["一段中文"]

// 5. 在方括号里,特殊字符大多失去特殊含义
/[.*+?{}()]/.test(".");    // true (这些元字符在 [] 里是字面值)
/[-]/.test("-");           // true (连字符放首尾也是字面值)
/[]]/.test("]");           // true (右方括号放首位是字面值)

一个反直觉但很重要的规则:在方括号内部,大部分元字符会自动失去特殊含义,变成字面值。点号、星号、加号、问号、圆括号在方括号里就是普通字符,不需要转义。唯一仍需小心的特殊字符是:右方括号(结束标志)、连字符(范围分隔)、脱字符(开头取反)、反斜杠(转义符)。

取反的妙用:排斥型匹配

取反字符类看似只是"反过来",但实战中它解决的是正则最容易出 bug 的场景——"贪婪跨过本应停止的位置"。下面这个对比是正则学习里的经典案例:

// 取反字符类的妙用

// [^x] 表示"除了 x 的任意字符"
/[^aeiou]/.test("b");     // true
/[^0-9]/.test("a");       // true

// 经典用法:排斥型匹配(比 . 更精确)
// 想匹配 HTML 标签里的内容,不要跨标签
/<.+>/.exec("<a><b><c>");     // ["<a><b><c>"] (. 太贪,跨了所有标签)
/<[^>]+>/.exec("<a><b><c>");  // ["<a>"]       (不含 > 的字符,自然不跨标签)

// 提取引号里的内容
/"[^"]+"/.exec('say "hello" then');   // ['"hello"']
/'[^']+'/.exec("it's 'fine'");         // ["'fine'"]

// 提取括号里的内容
/\([^)]+\)/.exec("foo(bar)baz");     // ["(bar)"]

原则是:能用具体的取反字符类,就别用通配点号。点号会匹配任何字符,引擎一不小心就跨过边界;而"非右尖括号的任意字符"自然不会跨过右尖括号,既快又安全。提取引号内容、括号内容、标签内容,统统用这个套路。

小结与速记表

下一章讲量词——把"匹配一个字符"升级成"匹配几个字符",你的正则才算真正"动起来"。

← 上一篇 基础语法与元字符

下一篇 量词

✈️💬