路径里有 id、通配后缀,有时还要看 hostname,自己 split 几次很容易漏边界。结尾要不要斜杠、大小写算不算同一条,这些细节用手写判断也容易对不齐。要判断一段 URL 能不能对上某条路由模式,并不想为此手写一堆字符串切割。
URL Pattern API 定义了一套用来创建 URL 模式匹配器的语法,可以拿完整地址或单个分量去比对。URLPattern 就是这套接口:test() 只回答命中没命中,exec() 返回匹配到的各部分,对不上则是 null。语法来自 path-to-regexp,模式里可以写字面量、通配符(/posts/*)、命名分组(/books/:id),以及用花括号包起来的非捕获分组。MDN 的例子里,new URLPattern({ pathname: "/books/:id" }) 去对 https://example.com/books/123 时,test() 为 true,exec() 得到的 pathname.groups 是 { id: "123" }。路径分量里,分组默认会匹配除 / 以外的字符。主机名里则默认不吃掉 .。分组还可以加修饰符:? 表示可有可无,+ 至少重复一次,* 可以重复零次或多次。未命名的正则分组会按出现顺序编成数字键。
构造时既可以传分量对象,也可以传带模式的完整 URL 字符串,再配可选的基础 URL。对象写法里没写的分量默认是通配符 *,所以只写 hostname 时,协议和路径不会把匹配卡死。传入基础地址之后,若输入里写了路径但没写协议和主机,协议、主机和端口可以从那里补上,查询和 hash 则不会补。用户名和密码也不会被继承。输入里一旦写了协议,就不会再从基础地址补其它分量。反过来,路径默认不会自动忽略末尾斜杠:/books 对不上 /books/,两边都要收的话,官方的写法是 /books{/}?。默认按大小写敏感来比对,构造函数可以打开 ignoreCase。通配符 * 本身是贪婪的,会尽量吃掉更长的一段。这和分组默认的非贪婪匹配不一样,写后缀模式时要想一下停在哪。也可以把模式写成完整地址字符串,MDN 举过 https://cdn-*.example.com/*.jpg 这类例子:协议和主机、路径一起写进构造函数,未写出的用户名、查询和 hash 会落到通配。只想拦某一类路径时,不必把协议和主机都写死。对象写法里只填 pathname 就够了。
它能管到的层是地址的各个分量:protocol、hostname、port、pathname、search、hash,以及 username、password。exec() 按分量交出匹配文本和分组,并不是只给一条路径字符串。未写出、也没有从基础 URL 继承到的模式分量默认是 *,测试地址上缺的分量则默认空字符串。因此只写了 pathname 时,search 也会落到通配,地址后面带不带查询都可能命中,这和手写「只判断路径」时的直觉不一定一样。模式里如果用了正则分组,hasRegExpGroups 会标出来,某些调用方不允许这类分组,需要提前看这个标记。浏览器支持方面,MDN 把它标成 Baseline 2025、Newly available,从 2025 年 9 月起在最新设备和浏览器中可用,更老的环境可能没有。
这和站内写过的 Import Maps 不是同一层:那份管的是模块路径怎么映射,这里是按模式去匹配一段 URL。它也不是某个框架路由器的替代品,只是浏览器提供的匹配器和分组提取。应用层怎么根据命中结果切页面、要不要再包一层自己的路由表,仍然是业务自己的选择。它只回答「对得上对不上、分组是什么」,并不负责把结果画成页面。
你们是只匹配 pathname,还是连 hostname 和查询字符串一起写进模式?