# URL Pattern API

网道（WangDoc.com），互联网文档计划

## 简介 #

URL Pattern API 基于正则表达式和通配符，对 URL 进行匹配和解析。

它提供一个构造函数`URLPattern()`，用于新建一个 URL 模式实例。

    const pattern = new URLPattern(input);

有了模式实例，就可以知道某个 URL 是否符合该模式。

    const pattern = new URLPattern({ pathname: "/books" });
    console.log(pattern.test("https://example.com/books")); // true

上面示例中，模式实例是 包含`/books`路径的 URL，实例方法`test()`用来检测指定网址是否符合该模式，结果为`true`。

URL Pattern 支持多种协议，不仅是 HTTP 协议。

    const pattern = new URLPattern("data\\:foo*");

上面示例中，URL Pattern 新建了一个 Data 协议的模式。

## 构造函数 URLPattern() #

### 基本用法 #

构造函数`URLPattern()`用于新建一个 URL 模式实例。

    const pattern = new URLPattern(input);

该构造函数的参数`input`是一个模式字符串或者模式对象。

    new URLPattern("https://example.com/books/:id")
    // {
    //   hasRegExpGroups: false,
    //   hash: "*",
    //   hostname: "example.com",
    //   password: "*",
    //   pathname: "/books/:id",
    //   port: "",
    //   protocol: "https",
    //   search: "*",
    //   username: "*",
    //   ...
    // }

上面示例中，参数`https://example.com/books/:id`就是一个模式字符串，执行后返回一个 URLPattern 实例对象，包含模式的各个组成部分。

参数`input`也可以写成一个对象，用属性指定模式 URL 的每个部分。也就是说，模式对象可以有以下属性。

- protocol
- username
- password
- hostname
- port
- pathname
- search
- hash
- baseURL

上面的示例，如果参数改成模式对象，就是下面这样。

    new URLPattern({
      protocol: 'https',
      hostname: 'example.com',
      pathname: '/books/:id',
    })

模式字符串或者模式对象之中，没有定义的部分，默认为`*`，表示所有可能的字符，包括零字符的情况。

`URLPattern()`正常情况下将返回一个 URLPattern 实例对象，但是遇到参数无效或语法不正确，则会报错。

    new URLPattern(123) // 报错

上面示例中，参数`123`不是一个有效的 URL 模式，就报错了。

需要注意的是，如果模式字符串为相对路径，那么`URLPattern()`还需要第二个参数，用来指定基准 URL。

    new URLPattern(input, baseURL)

上面代码中，第二个参数`baseURL`就是基准 URL。

    new URLPattern('/books/:id') // 报错
    new URLPattern('/books/:id', 'https://example.com') // 正确

上面示例中，第一个参数`/books/:id`是一个相对路径，这时就需要第二个参数`https://example.com`，用来指定基准 URL，否则报错。

但是，如果参数为模式对象，则可以只指定 URL 模式的某个部分。

    new URLPattern({
      pathname: '/books/:id'
    }) // 正确

上面示例中，参数是一个模式对象，那么参数允许只指定 URL 的部分模式。

模式对象里面，也可以指定基准 URL。

    let pattern4 = new URLPattern({
      pathname: "/books/:id",
      baseURL: "https://example.com",
    });

基准 URL 必须是合法的 URL，不能包含模式。

注意，如果用了模式对象，就不能使用基准 URL 作为第二个参数，这样会报错。

    new URLPattern({ pathname: "/foo/bar" }, "https://example.com") // 报错
    new URLPattern({ pathname: "/foo/bar" }, "https://example.com/baz") // 报错

上面示例中，同时使用了模式对象和第二个参数，结果就报错了。

`URLpattern()`还可以加入配置对象参数，用于定制匹配行为。

    new URLPattern(input, options)
    new URLPattern(input, baseURL, options)

上面代码中，参数`options`就是一个配置对象。

目前，这个配置对象`options`只有`ignoreCase`一个属性，如果设为`true`，将不区分大小写，默认值为`false`，表示区分大小写。

    new URLPattern(input, {
      ignoreCase: false // 默认值，区分大小写
    })

请看下面的例子。

    const pattern = new URLPattern("https://example.com/2022/feb/*");

    pattern.test("https://example.com/2022/feb/xc44rsz") // true
    pattern.test("https://example.com/2022/Feb/xc44rsz") // false

上面示例，默认匹配时，会区分`feb`和`Feb`。

我们可以用`ignoreCase`将其关闭。

    const pattern = new URLPattern(
      "https://example.com/2022/feb/*",
      {  ignoreCase: true, }
    );

    pattern.test("https://example.com/2022/feb/xc44rsz") // true
    pattern.test("https://example.com/2022/Feb/xc44rsz") // true

### 模式写法 #

模式字符串基本上采用正则表达式的写法，但是不是所有的正则语法都支持，比如先行断言和后行断言就不支持。

（1）普通字符

如果都是普通字符，就表示原样匹配。

    const p = new URLPattern('https://example.com/abc');

上面代码就表示确切匹配路径`https://example.com/abc`。

    p.test('https://example.com') // false
    p.test('https://example.com/a') //false
    p.test('https://example.com/abc') // true
    p.test('https://example.com/abcd') //false
    p.test('https://example.com/abc/') //false
    p.test('https://example.com/abc?123') //true

上面示例中，URL 必须严格匹配路径`https://example.com/abc`，即使尾部多一个斜杠都不行，但是加上查询字符串是可以的。

（2）`?`

量词字符`?`表示前面的字符串，可以出现0次或1次，即该部分可选。

    let pattern = new URLPattern({
      protocol: "http{s}?",
    });

上面示例中，`{s}?`表示字符组`s`可以出现0次或1次。

`?`不包括路径的分隔符`/`。

    const pattern = new URLPattern("/books/:id?", "https://example.com");

    pattern.test("https://example.com/books/123") // true
    pattern.test("https://example.com/books") // true
    pattern.test("https://example.com/books/") // false
    pattern.test("https://example.com/books/123/456") // false
    pattern.test("https://example.com/books/123/456/789") // false
    pattern.test("https://example.com/books/123/456/") // false

上面示例中，`?`不能匹配网址结尾的斜杠。

如果一定要匹配，可以把结尾的斜杠放在`{}`里面。

    const pattern = new URLPattern({ pathname: "/product{/}?" });

    pattern.test({ pathname: "/product" }) // true
    pattern.test({ pathname: "/product/" }) // true

上面示例中，不管网址有没有结尾的斜杠，`{/}?`都会成功匹配。

（3）`+`

量词字符`+`表示前面的字符串出现1次或多次。

    const pattern = new URLPattern({
      pathname: "/books/(\\d+)",
    })

上面示例中，`\\d+`表示1个或多个数字，其中的`\d`是一个内置的字符类，表示0-9的数字，因为放在双引号里面，所以反斜杠前面还要再加一个反斜杠进行转义。

`+`可以包括`/`分隔的路径的多个部分，但不包括路径结尾的斜杠。

    const pattern = new URLPattern("/books/:id+", "https://example.com");

    pattern.test("https://example.com/books/123") // true
    pattern.test("https://example.com/books") // false
    pattern.test("https://example.com/books/") // false
    pattern.test("https://example.com/books/123/456") // true
    pattern.test("https://example.com/books/123/456/789") // true
    pattern.test("https://example.com/books/123/456/") // false

（4）`*`

量词字符`*`表示出现零次或多次。

    const pattern = new URLPattern('https://example.com/{abc}*');

    pattern.test('https://example.com') // true
    pattern.test('https://example.com/') // true
    pattern.test('https://example.com/abc') // true
    pattern.test('https://example.com/abc/') // false
    pattern.test('https://example.com/ab') // false
    pattern.test('https://example.com/abcabc') // true
    pattern.test('https://example.com/abc/abc/abc') // false

上面示例中，`{abc}*`表示`abc`出现零次或多次，也不包括路径分隔符`/`。

如果`*`前面没有任何字符，就表示所有字符，包括零字符的情况，也包括分隔符`/`。

    let pattern = new URLPattern({
      search: "*",
      hash: "*",
    });

上面示例中，`*`表示匹配所有字符，包括零字符。

下面是另一个例子。

    const pattern = new URLPattern("/*.png", "https://example.com");

    pattern.test("https://example.com/image.png") // true
    pattern.test("https://example.com/image.png/123") // false
    pattern.test("https://example.com/folder/image.png") // true
    pattern.test("https://example.com/.png") // true

`*`匹配的部分可以从对应部分的数字属性上获取。

    const pattern = new URLPattern({
      hostname: "example.com",
      pathname: "/foo/*"
    });

    const result = pattern.exec("/foo/bar", "https://example.com/baz");

    result.pathname.input // '/foo/bar'
    result.pathname.groups[0] // 'bar'

上面示例中，`*`的匹配结果可以从`pathname.groups[0]`获取。

    const pattern = new URLPattern({ hostname: "*.example.com" });
    const result = pattern.exec({ hostname: "cdn.example.com" });

    result.hostname.groups[0] // 'cdn'
    result.hostname.input // 'cdn.example.com'

上面示例中，`*`的匹配结果可以从`hostname.groups[0]`获取。

（5）`{}`

特殊字符`{}`用来定义量词`?`、`+`、`+`的生效范围。

如果`{}`后面没有量词，那就跟没有使用的效果一样。

    const pattern = new URLPattern('https://example.com/{abc}');

    pattern.test('https://example.com/') // false
    pattern.test('https://example.com/abc') // true

（6）`()`

特殊字符`()`用来定义一个组匹配，匹配结果可以按照出现顺序的编号，从`pathname.groups`对象上获取。

    const pattern = new URLPattern("/books/(\\d+)", "https://example.com");
    pattern.exec("https://example.com/books/123").pathname.groups
    // { '0': '123' }

上面示例中，`(\\d+)`是一个组匹配，因为它是第一个组匹配，所以匹配结果放在`pathname.groups`的属性`0`。

（7）`|`

特殊字符`|`表示左右两侧的字符，都可以出现，即表示逻辑`OR`。

    let pattern = new URLPattern({
      port: "(80|443)",
    });

上面示例中，`(80|443)`表示80或者443都可以。

（8）`:`

特殊字符`:`用来定义一个具名组匹配，后面跟着变量名。

    let pattern = new URLPattern({
      pathname: "/:path",
    });

上面示例中，`/:path`表示斜杠后面的部分，都被捕捉放入变量`path`，可以从匹配结果的`pathname.groups`上的对应属性获取。

    const pattern = new URLPattern({ pathname: "/books/:id" });

    pattern.exec("https://example.com/books/123").pathname.groups
    // { id: '123' }

上面示例中，`pathname.groups`返回一个对象，该对象的属性就是所有捕捉成功的组变量，上例是`id`。

下面是另一个例子。

    const pattern = new URLPattern({ pathname: "/:product/:user/:action" });
    const result = pattern.exec({ pathname: "/store/wanderview/view" });

    result.pathname.groups.product // 'store'
    result.pathname.groups.user // 'wanderview'
    result.pathname.groups.action // 'view'
    result.pathname.input // '/store/wanderview/view'

上面示例中，`:product`、`:user`、`:action`的匹配结果，都可以从`pathname.groups`的对应属性上获取。

组匹配可以放在模式的前面。

    const pattern = new URLPattern(
      "/books/:id(\\d+)",
      "https://example.com"
    );

上面示例中，组匹配`:id`后面跟着模型定义`\\d+`，模式需要放在括号里面。

（9）特殊字符转义

如果要将特殊字符当作普通字符使用，必须在其前面加入双重反斜杠进行转义。

    let pattern1 = new URLPattern({
      pathname: "/a:b",
    });

    let pattern2 = new URLPattern({
      pathname: "/a\\:b",
    });

上面示例中，`a:b`表示路径以字符`a`开头，后面的部分都放入变量`b`。而`a\\:b`表示路径本身就是`a:b`就是。

## 实例属性 #

URLPattern 实例的属性对应`URLPattern()`模式对象参数的各个部分。

    const pattern = new URLPattern({
      hostname: "{*.}?example.com",
    });

    pattern.hostname // '{*.}?example.com'
    pattern.protocol // '*'
    pattern.username // '*'
    pattern.password // '*'
    pattern.port // ""
    pattern.pathname // '*'
    pattern.search // '*'
    pattern.hash // '*'

上面示例中，`pattern`是一个实例对象，它的属性与`URLPattern()`的参数对象的属性一致。

注意，`search`不包括开头的`?`，`hash`不包括开头的`#`，但是`pathname`包括开头的`/`。

下面是另一个例子。

    const pattern = new URLPattern("https://cdn-*.example.com/*.jpg");

    pattern.protocol // 'https'
    pattern.hostname // 'cdn-*.example.com'
    pattern.pathname // '/*.jpg'
    pattern.username // ''
    pattern.password // ''
    pattern.search // ''
    pattern.hash // ''

## 实例方法 #

### exec() #

实例的`exec()`方法，把模式用于解析参数网址，返回匹配结果。

`exec()`方法的参数与`new URLPattern()`是一致的。它可以是一个 URL 字符串。

    pattern.exec("https://store.example.com/books/123");

如果第一个参数是相对 URL，那么需要基准 URL，作为第二个参数。

    pattern.exec("/foo/bar", "https://example.com/baz");

`exec()`方法的参数，也可以是一个对象。

    pattern.exec({
      protocol: "https",
      hostname: "store.example.com",
      pathname: "/books/123",
    });

如果匹配成功，它返回一个包括匹配结果的对象。如果匹配失败，返回`null`。

    const pattern = new URLPattern("http{s}?://*.example.com/books/:id");
    pattern.exec("https://example.com/books/123") // null

上面示例中，匹配失败返回`null`。

匹配成功返回的对象，有一个`inputs`属性，包含传入`pattern.exec()`的参数数组。其他属性的值也是一个对象，该对象的`input`属性对应传入值，`groups`属性包含各个组匹配。

    const pattern = new URLPattern("http{s}?://*.example.com/books/:id");
    let match = pattern.exec("https://store.example.com/books/123");

    match.inputs // ['https://store.example.com/books/123']
    match.protocol // { input: "https", groups: {} }
    match.username // { input: "", groups: {} }
    match.password // { input: "", groups: {} }
    match.hostname // { input: "store.example.com", groups: { "0": "store" } }
    match.port // { input: "", groups: {} }
    match.pathname // { input: "/books/123", groups: { "id": "123" } }
    match.search // { input: "", groups: {} }
    match.hash // { input: "", groups: {} }

### test() #

实例的`test()`方法，用来检测参数网址是否符合当前模式。

它的参数跟`URLPattern()`是一样的，可以是模式字符串，也可以是模式对象。

    const pattern = new URLPattern({
      hostname: "example.com",
      pathname: "/foo/*"
     });

    pattern.test({
      pathname: "/foo/bar",
      baseURL: "https://example.com/baz",
    }) // true

    pattern.test("/foo/bar", "https://example.com/baz") // true

正常情况下，它返回一个布尔值。但是，如果语法不合法，它也会抛错。

    pattern.test({ pathname: "/foo/bar" }, "https://example.com/baz") // 报错

本教程采用知识共享 署名-相同方式共享 3.0协议。

分享本文