以为URL校验必须try/catch?浏览器今天把这件事彻底变了
很多前端项目里都有这样的代码——用户输入一个链接,你要校验它是不是合法 URL,要拿它的 hostname 去做展示或者校验,要拿到 pathname 去做路由……这些场景下,new URL() 是你唯一的选择。
问题是:new URL() 是个构造函数,构造失败时它会抛异常。
这就意味着,每一次你校验用户输入的 URL,你都要套一层 try/catch:
let url;
try {
url = new URL(userInput);
} catch {
// 告诉用户 URL 不合法
return;
}
// 继续用 url
这段代码的问题是:为了一个「无效输入是正常预期」的场景,你把控制流硬掰成了一个 try/catch 块,代码可读性瞬间下降,出错点也从「URL 无效」转移到了「异常处理漏了」。
2024 年,URL.parse() 被正式纳入了 WHATWG 标准,Chrome 126、Firefox 126、Safari 18 全部上线,Baseline 2024。这件事把 URL 校验的工作流彻底变了。
URL.parse() 怎么用
URL.parse() 是 URL 接口的静态方法,签名长这样:
URL.parse(url: string | URL, base?: string | URL): URL | null
第一个参数是你要解析的 URL 字符串,第二个参数可选——当第一个参数是相对路径时,用 base 解析出绝对 URL。如果 URL 无效,直接返回 null,不抛异常。
用法极度干净:
const url = URL.parse(userInput);
if (url === null) {
// 告诉用户 URL 不合法
return;
}
// url 是合法的 URL 对象
console.log(url.hostname); // "example.com"
console.log(url.pathname); // "/posts/123"
没有 try/catch,没有 let 声明,没有控制流分叉——一个 if 干完所有事情。
url 和 base 参数都支持任意有 toString() 方法的对象,包括现有的 URL 实例。这意味着如果你有一个 URL 对象,想基于它生成新的 URL,可以直接传进去:
const base = URL.parse("https://example.com/docs/");
const parsed = URL.parse("guide/install", base);
console.log(parsed.href); // "https://example.com/docs/guide/install"
旧世界的三种做法
在 URL.parse() 出现之前,社区里校验 URL 已经是工程问题了,大家摸索出了三种常见模式:
模式一:try/catch 包裹
function safeParseURL(input) {
try {
return new URL(input);
} catch {
return null;
}
}
这是最常见的做法。很多项目里都有个 safeParseURL 或者 tryParseUrl 之类的工具函数,每次校验都要调这个函数,函数本身又是个 try/catch——代码在两个地方各自承担了异常处理的心理负担。
模式二:URL.canParse() 先检查再构造
if (URL.canParse(userInput)) {
const url = new URL(userInput);
// …
}
URL.canParse() 在 2023 年底已经获得了跨浏览器支持(Baseline Widely Available),这是一个改进。但它多了一步:你要先问「能不能解析」,然后再「解析」,两步变一步。
模式三:短路求值
const url = URL.canParse(userInput) && new URL(userInput);
这是模式二的变体,在一行里解决。但可读性仍然不如直接一个方法返回 null。
URL.parse() 把这三种模式全部归一了。 同样的场景:
const url = URL.parse(userInput);
if (!url) return;
一行顶原来三到四行,不需要维护工具函数,不需要两步操作,null 检查天然内嵌在返回值的类型里。
TypeScript 类型收窄
URL.parse() 在 TypeScript 5.1(2023 年 5 月)里已经添加了类型签名,DOM lib 里直接就有,不需要任何额外 @types:
function getCanonicalHost(input: string): string | null {
const url = URL.parse(input);
return url ? url.hostname : null;
}
这里 TypeScript 知道 URL.parse() 返回 URL | null,所以 if (url) 这个条件判断直接触发了类型收窄,url 在条件分支内部自动变成 URL 类型,不需要手动类型断言。
如果你在用 TypeScript,这是一个立即可见的收益——原来你可能要给 safeParseURL 写 URL | null 的返回类型,现在直接删掉那个工具函数,URL.parse() 的类型签名已经告诉你答案了。
什么场景用哪个
用 new URL() 的场景:
输入来自你代码里自己写的字面量、配置文件、模板字符串——这些输入是你自己保证合法的,不需要异常处理,直接构造。
const API_BASE = new URL("https://api.example.com"); // 合法,放心构造
用 URL.parse() 的场景:
任何来自用户输入、外部数据、API 响应的字符串——这些都不能保证合法,用 URL.parse() 处理无效情况:
// 用户输入 / 表单提交
const url = URL.parse(formInput.value);
if (!url) { showError("链接格式不正确"); return; }
// 解析相对路径
const resolved = URL.parse(link.pathname, window.location.href);
兼容性与生产环境
URL.parse() 是 Baseline 2024,浏览器支持情况:
| 环境 | 版本要求 |
|---|---|
| Chrome | 126+(2024 年 6 月) |
| Firefox | 126+(2024 年 6 月) |
| Safari | 18+(2024 年 9 月) |
| Node.js | 22.1+ |
| Deno | 1.43+ |
| Bun | 1.1.4+ |
Web Workers 里同样可用——这对做离线路由表或者 Service Worker 里解析 URL 的场景特别有用。
对于还在用旧版浏览器的用户,core-js 里有 URL.parse() 的 polyfill,直接引入即可。迁移路径是:先 feature detect,再渐进替换你项目里那些 safeParseURL 工具函数:
// 旧
const url = safeParseURL(input);
// 新(兼容写法)
const url = typeof URL.parse === "function"
? URL.parse(input)
: safeParseURL(input);
迁移行动清单
- 搜索你的代码库里叫 safeParseURL、tryParseUrl、parseUrlSafely 之类的函数——这些是第一批可以被替换的目标
- 搜 new URL() 被 try/catch 包裹的地方——检查是否能直接改成 URL.parse(),然后把 try/catch 删掉
- 检查 TypeScript 里的类型断言——URL.parse() 返回
URL | null,TypeScript 会自动收窄类型,原来手动写的as URL大概率可以删掉 - 保留 new URL() 给受信任的输入用——配置里的 base URL、自己拼接的字符串字面量,这些不需要改
一句话总结: URL.parse() 不是一个新玩具,它把 JavaScript 里校验 URL 这件事从「异常处理」变成了「正常返回」,让控制流回归直线,也让代码审查时不用再看到第三层嵌套的 catch 块。这件事从 2018 年有人在 WHATWG 提 issue 到 2024 年三引擎全部上线,走了六年。现在可以用了。
评论区
登录后可评论。