丙午🐎年

acc8226 的博客

一、基础元字符

字符 含义 示例
. 任意单个字符(除换行) a.cabc, a1c
\d 数字 [0-9] \d{3}123
\D 非数字 [^0-9] \D+abc
\w 单词字符 [a-zA-Z0-9_] \w+hello_world
\W 非单词字符 \W@, #,
\s 空白字符(空格、Tab、换行) \s+
\S 非空白字符 \S+abc
\b 单词边界 \bword\b 精确匹配 word
\B 非单词边界

二、量词(匹配次数)

量词 含义 等价写法
* 0 次或多次 {0,}
+ 1 次或多次 {1,}
? 0 次或 1 次 {0,1}
{n} 恰好 n 次 \d{3}123
{n,} 至少 n 次 \w{3,}
{n,m} n 到 m 次 \d{3,8}

三、字符类与范围

1
2
3
4
5
6
7
[abc]       // a 或 b 或 c
[^abc] // 非 a、b、c
[a-z] // a 到 z
[A-Z] // A 到 Z
[0-9] // 0 到 9
[a-zA-Z] // 所有字母
[a-zA-Z0-9_] // 等价于 \w

[] 内,大部分特殊字符失去特殊含义

1
2
[.*+?]      // 匹配字面量 . * + ?
[-a-z] // - 放开头或结尾表示字面量

四、边界与位置

符号 含义
^ 字符串开头(多行模式下是行首)
$ 字符串结尾(多行模式下是行尾)
\b 单词边界
\B 非单词边界
(?=...) 正向前瞻
(?!...) 负向前瞻
(?<=...) 正向后顾(ES2018)
(?<!...) 负向后顾(ES2018)
1
2
3
4
5
// 前瞻:匹配后面跟着 "Script" 的单词
/[a-zA-Z]+(?=Script)/g.exec('JavaScript'); // ['Java']

// 后顾:匹配前面是 "$" 的数字(ES2018)
/(?<=\$)\d+/.exec('Price: $100'); // ['100']

五、分组与捕获

语法 含义
(...) 捕获分组
(?:...) 非捕获分组(不保存到结果)
\1, \2 反向引用第 n 个分组
(?<name>...) 命名捕获组(ES2018)
1
2
3
4
5
6
7
let re = /^(?<area>\d{3})-(?<number>\d{3,8})$/;
let result = re.exec('010-12345');
console.log(result.groups.area); // '010'
console.log(result.groups.number); // '12345'

// 反向引用:匹配重复单词
/\b(\w+)\s+\1\b/.test('hello hello'); // true

六、贪婪 vs 非贪婪

模式 符号 行为
贪婪(默认) *, +, ?, {n,m} 尽可能多匹配
非贪婪 *?, +?, ??, {n,m}? 尽可能少匹配
1
2
3
4
let html = '<div>content</div><span>text</span>';

/<.*>/.exec(html)[0]; // '<div>content</div><span>text</span>' 贪婪
/<.*?>/.exec(html)[0]; // '<div>' 非贪婪

七、JavaScript 正则标志(Flags)

| 标志 | 含义 | 说明 |----------- |
| g | global 全局匹配 | exec() 多次调用,更新 lastIndex |
| i | ignoreCase 忽略大小写 | |
| m | multiline 多行模式 | ^$ 匹配每行开头结尾 |
| s | dotAll 单行模式 | . 匹配换行符(ES2018) |
| u | unicode Unicode 模式 | 正确处理 Unicode(ES2015) |
| y | sticky 粘性匹配 | 从 lastIndex 开始严格匹配(ES2015) |

1
2
3
4
5
let re = /test/gi;           // 全局 + 忽略大小写
let re2 = new RegExp('test', 'gi'); // 等价

// dotAll 模式(ES2018)
/foo.bar/s.test('foo\nbar'); // true,. 能匹配 \n

八、JS 正则 API

1. RegExp.prototype.test(str) — 布尔判断

1
2
/^\d+$/.test('123');  // true
/^\d+$/.test('abc'); // false

2. RegExp.prototype.exec(str) — 提取匹配

1
2
3
4
5
6
7
let re = /(\d{3})-(\d{3,8})/;
let result = re.exec('010-12345');
// result[0] = '010-12345' 完整匹配
// result[1] = '010' 第1个捕获组
// result[2] = '12345' 第2个捕获组
// result.index = 0 匹配位置
// result.input = '010-12345' 原始字符串

3. String.prototype.match(regexp) — 匹配数组

1
2
3
4
5
'abc123def456'.match(/\d+/g);  // ['123', '456']

// 无 g 标志时,类似 exec,但返回数组
'010-12345'.match(/(\d{3})-(\d{3,8})/);
// ['010-12345', '010', '12345', index: 0, input: '010-12345', groups: undefined]

4. String.prototype.matchAll(regexp) — 迭代所有匹配(ES2020)

1
2
3
4
5
6
let str = 'JavaScript, VBScript, JScript';
let re = /[a-zA-Z]+Script/g;

for (const match of str.matchAll(re)) {
console.log(match[0]); // 'JavaScript', 'VBScript', 'JScript'
}

5. String.prototype.search(regexp) — 查找位置

1
2
'hello world'.search(/world/);  // 6
'hello world'.search(/xyz/); // -1

6. String.prototype.replace(regexp, replacement) — 替换

1
2
3
4
5
6
7
'2024-06-20'.replace(/-/g, '/');  // '2024/06/20'

// 使用捕获组
'010-12345'.replace(/(\d{3})-(\d{3,8})/, '($1) $2'); // '(010) 12345'

// 使用函数处理
'abc123def456'.replace(/\d+/g, match => `[${match}]`); // 'abc[123]def[456]'

7. String.prototype.split(separator) — 分割

1
'a,b;; c  d'.split(/[\s,;]+/);  // ['a', 'b', 'c', 'd']

九、实用正则大全

Email(基础版)

1
/^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/

手机号(中国大陆)

1
/^1[3-9]\d{9}$/

身份证号

1
2
/^\d{15}|\d{18}$/           // 简单版
/^[1-9]\d{5}(18|19|20)\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])\d{3}[\dXx]$/ // 严格版

URL

1
/^https?:\/\/[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}(\/\S*)?$/

中文字符

1
2
/[\u4e00-\u9fa5]+/           // 基础中文
/\p{Script=Han}/u // Unicode 属性(ES2018,更完整)

密码强度(8-20位,至少包含字母和数字)

1
/^(?=.*[A-Za-z])(?=.*\d)[A-Za-z\d@$!%*?&]{8,20}$/

去除 HTML 标签

1
html.replace(/<[^>]+>/g, '')

提取 Markdown 链接

1
/\[([^\]]+)\]\(([^)]+)\)/g   // [text](url)

十、性能优化与陷阱

陷阱 说明 解决
回溯灾难 (a+)+b 匹配超长字符串时指数级回溯 使用原子组(JS 不支持)或简化模式
贪婪导致过度匹配 .* 匹配太多 .*? 非贪婪,或限定范围
Unicode 问题 \w 不匹配中文 [\u4e00-\u9fa5]\p{L}u 标志)
多行匹配 ^$ 默认只匹配首尾 m 标志
lastIndex 陷阱 g 标志的 RegExp 会记住位置 每次 test/exec 前重置 lastIndex = 0
1
2
3
4
5
let re = /\d+/g;
re.test('123'); // true
re.test('123'); // false! 因为 lastIndex 已经到末尾
re.lastIndex = 0; // 重置
re.test('123'); // true

十一、ES 新特性时间线

特性 版本 说明
u (Unicode) ES2015 \u{1F600} 正确匹配 Emoji
y (Sticky) ES2015 粘性匹配
s (dotAll) ES2018 . 匹配换行
命名捕获组 (?<name>) ES2018 result.groups.name
后顾断言 (?<=) (?<!) ES2018 向前/向后查找
matchAll ES2020 返回迭代器,替代 while + exec

十二、一句话总结

正则本质是"模式描述语言":用元字符描述规则,用量词控制次数,用分组提取信息,用标志控制行为。JS 中优先用字面量 /.../,需动态构建时用 new RegExp()。复杂场景(如 Email 完整验证)建议用专业库,正则负责简单高效的匹配。

一个示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
let re = /^\d{3}-\d{3,8}$/;
console.log(re.test('010-12345')); // true
console.log(re.test('010-1234x')); // false
console.log(re.test('010 12345')); // false

console.log('a b c'.split(/\s+/)); // ['a', 'b', 'c']

re = /^(\d{3})-(\d{3,8})$/;
console.log(re.exec('010-12345')); // ['010-12345', '010', '12345']
console.log(re.exec('010 12345')); // null

re = /^(\d+)(0*)$/;
console.log(re.exec('102300')); // ['102300', '102300', '']
// 非贪婪匹配
re = /^(\d+?)(0*)$/;
console.log(re.exec('102300')); // ['102300', '1023', '00']

参考

时间和时间戳

在 JavaScript 中,Date对象用来表示日期和时间。

要获取系统当前时间,用:

1
2
3
4
5
6
7
8
9
10
11
let now = new Date();
now; // Wed Jun 24 2015 19:49:22 GMT+0800 (CST)
now.getFullYear(); // 2015, 年份
now.getMonth(); // 5, 月份,注意月份范围是0~11,5表示六月
now.getDate(); // 24, 表示24号
now.getDay(); // 3, 表示星期三
now.getHours(); // 19, 24小时制
now.getMinutes(); // 49, 分钟
now.getSeconds(); // 22, 秒
now.getMilliseconds(); // 875, 毫秒数
now.getTime(); // 1435146562875, 以number形式表示的时间戳

注意,当前时间是浏览器从本机操作系统获取的时间,所以不一定准确,因为用户可以把当前时间设定为任何值。

阅读全文 »

基本上所有的高级语言都支持函数,JavaScript 也不例外。JavaScript 的函数不但是“头等公民”,而且可以像变量一样使用,具有非常强大的抽象能力。

借助抽象,我们才能不关心底层的具体计算过程,而直接在更高的层次上思考问题。写计算机程序也是一样,函数就是最基本的一种代码抽象的方式。

阅读全文 »

Deno (/ˈdiːnoʊ/, pronounced dee-no) 是一个开源的 JavaScript、TypeScript 和 WebAssembly 运行时,具有安全默认设置和出色的开发体验。它构建于 V8RustTokio

让我们在五分钟内创建并运行您的第一个 Deno 程序。

安装完成后,您的系统路径中应该有 deno 可执行文件。您可以通过运行以下命令来验证安装:deno --version

阅读全文 »
0%