系统设计 / 路由设计 · 稳定入口与可变目标 / URLPattern · 浏览器内建的 URL matcher 待审核 12 / 23

URLPattern · 浏览器内建的 URL matcher

匹配引擎手写了一台 path 到 handler 的引擎:按 / 切段、逐段对齐、提取 :paramURLPattern 是浏览器把这台引擎标准化、内建的原生 API,能力远超手工版本。

两点不同最关键。一是匹配范围:从只切 pathname 一段,扩到整条 URL 的八个组件——protocolusernamepasswordhostnameportpathnamesearchhash,全部命中才算命中。二是语法:从 :param* 两种,扩到带正则约束的命名组 :id(\d+)、可选分组 {/:slug}?、可重复段 :rest* 与枚举 (en|zh)

本页两个 lab 都在真实调用浏览器里的 window.URLPattern,即时构造 new URLPattern(...)、用 test() 判命中、用 exec() 取出命名组。它已进入各大浏览器:Chrome 与 Edge 95+、Firefox 142+(2025-08)、Safari 26+(2025-09),Deno、Bun 与 Workers 侧也都有(兼容性据 MDN BCD,核对于 2026-08)。

1 · pathname 上的语法

图 1-1 · 各种 segment 语法在 pathname 上的匹配结果。可改 pattern 或输入,当场看 test() 的判定与 exec() 取出的命名组。

2 · 语法速查

这套语法来自 path-to-regexp 一脉,React Router 与 Express 用户会很眼熟,区别是 URLPattern 把它写进了 Web 标准。
语法 含义
:name 命名组,吃一段(默认不跨 / /users/:id/users/42{ id: "42" }
:name(regex) 给这一段加正则约束 :id(\d+) 只配数字;/users/abc 直接 miss
* 通配,吃掉剩余;是匿名组,组名为字符串 "0" /files/*/files/a/b.png
:name* · :name+ 可重复段(0+ / 1+),把跨段的剩余收进命名组 /files/:rest*{ rest: "a/b.png" }
{…}? 可选分组,默认时该组值为 undefined /posts{/:slug}? 同时配 /posts/posts/x
(a|b) 枚举,本质是正则约束 /:lang(en|zh)/about 只接受 en 与 zh

3 · 整条 URL 都能写 pattern

手工实现的 matcher 只认 pathname 那一段。URLPattern 的 pattern 是个多组件对象,八个组件各写各的、全部命中才算命中,任一组件没写则默认 * 匹配任意。日常用到的通常是 protocolhostnameportpathnamesearchhash 这六个,usernamepassword 对应 URL 里的认证段,很少写。

图 3-1 · 固定 `protocol: \

4 · 真实用法

test() 判命中、exec() 取每个组件的命名组。

// 浏览器内建的 URL matcher —— 不必再手搓 segsOf / matchPattern
const p = new URLPattern({ pathname: '/users/:id(\\d+)' });

p.test({ pathname: '/users/42' });    // → true
p.test({ pathname: '/users/abc' });   // → false   (:id 限定 \\d+)

const m = p.exec({ pathname: '/users/42' });
m.pathname.groups;                    // → { id: '42' }

// 不止 pathname: 整条 URL 的每个组件都能写 pattern, 全部命中才算命中
const api = new URLPattern({
  protocol: 'https',
  hostname: ':sub.example.com',       // 连子域都能作为命名组提取
  pathname: '/api/:version/users/:id(\\d+)',
});
api.exec('https://app.example.com/api/v2/users/42');
// → { hostname: { groups: { sub: 'app' } },
//     pathname: { groups: { version: 'v2', id: '42' } }, ... }

// 也能直接用一整条 URL 字符串构造 (没写的组件默认 * 通配)
new URLPattern('https://*.example.com/api/*');

5 · 与手工实现的对照

匹配引擎一页讲的是这台引擎内部怎么跑,本页讲的是浏览器已经把它做好了,而且更全。
这件事 手工实现的 matcher URLPattern
匹配范围 只切 pathname 一段 整条 URL 八个组件,全部命中才算命中
命名参数 :id 吃一段 :id 一样,但可加正则约束 :id(\d+)
通配 * 吃剩余 * / :rest* / :rest+,还能带前后缀
可选段 不支持——得写两条路由 {/:id}? 内建可选,一条搞定
提取结果 自己拼 params 对象 exec() 给每个组件的 .groups
来源 自己维护的几十行代码 浏览器内建的 Web 标准

警示 · 四处容易踩的地方,下列判定均在 Chrome 151 上实测(核对于 2026-08)。:name 默认不跨 /,只吃一段,要跨段收剩余得用 *:name*——new URLPattern({pathname:'/users/:id'})/users/a/b 返回 false* 的捕获结果是匿名组,落在 groups["0"] 而不是某个名字。JS 源码里写 :id(\\d+) 才得到正则 \d+,本页输入框里写一个反斜杠即可,因为读的是 input.value。最后,pattern 是 full match 而非子串搜索,{pathname:'/users'}/users/42 返回 false;而没写的组件默认 *,容易误以为限制住了而实际没有。

Navigation API 接住导航事件、URLPattern 负责匹配,两者常一起搭出新一代 client router,见 Navigation API。URLPattern 的多组件正好对应一条 URL 的各块,URL Anatomy 系列逐段拆开讲。