你有没有写过这样的代码?
const query = window.location.search.slice(1);const pairs = query.split('&');const params = {};pairs.forEach(pair => { const [key, val] = pair.split('='); params[key] = decodeURIComponent(val);});
或者这样:
const url = 'https://api.example.com/users?page=' + page + '&limit=' + limit;
这些写法虽然能用,但隐藏着许多坑:特殊字符编码遗漏、重复参数处理不当、相对路径解析错误……更重要的是,它们让代码变得脆弱且难以维护。
好消息是,浏览器早就为我们准备了一套标准 API,专门用来 安全、轻松地驾驭 URL。这套 API 就是 URL 和 URLSearchParams。掌握它们,你不仅能彻底告别手写解析逻辑,还能让代码更清晰、更健壮。接下来,我们就一步步走进这个实用工具箱。
一、认识 URL 的真面目
1.1 创建一个 URL 对象
要使用 URL API,第一步就是实例化一个 URL 对象:
const url = new URL('https://example.com:8080/path/to/page?id=123&name=追风#section');
构造函数接收两个参数:
const url = new URL('/api/users', 'https://example.com');console.log(url.href);
注意:base 参数必须是 绝对 URL,否则会抛出 TypeError。
1.2 URL 的“骨骼结构”
一个 URL 就像一栋房子,有门牌号、楼层、房间号等组成部分。URL 对象把这些部分都暴露成了属性,方便你直接读取或修改,一个完整的 URL 可以拆解为以下各部分:
// 示例urlhttps://user:pass@example.com:8080/path/to/page?id=123#section\___/ \_______/ \_________/ \__/\____________/ \______/ \______/ | | | | | | |protocol username hostname port pathname search hash
属性 | 作用 | 示例值 |
href | 完整 URL 字符串 | 见示例 url |
protocol | 协议(含冒号) | https: |
username | 用户名 | user |
password | 密码 | pass |
host | 主机+端口 | example.com:8080 |
hostname | 主机名 | example.com |
port | 端口号 | 8080 |
pathname | 路径 | /path/to/page |
search | 查询字符串(含?) | ?id=123&name=追风 |
searchParams | URLSearchParams 对象 | 见下节 |
hash | 片段(含#) | #section |
origin | 来源(只读) | https://example.com:8080 |
示例:
const url = new URL('https://user:pass@example.com:8080/path/to/page?id=123#section');console.log(url.protocol); console.log(url.username); console.log(url.host); console.log(url.hostname); console.log(url.port); console.log(url.pathname); console.log(url.search); console.log(url.hash); console.log(url.origin);
看到这里,你会发现:再也不用自己写正则去抠 URL 的各个部分了——浏览器都帮你拆好了。
二、查询参数:从“手忙脚乱”到“游刃有余”
查询参数(?key=value&...)是 URL 中最常变动的部分,也是容易出错的重灾区。URLSearchParams 就是为此而生的专属工具。
2.1 获取参数对象
你既可以从已有的 URL 实例中拿,也可以独立创建:
const url = new URL('https://example.com?id=123&name=追风&tags=js&tags=web');const params = url.searchParams;
const p1 = new URLSearchParams('id=123&name=追风');const p2 = new URLSearchParams({ id: '123', name: '追风' });const p3 = new URLSearchParams([['id', '123'], ['name', '追风']]);
2.2 增删改查,一气呵成
URLSearchParams 提供了一套完整的方法,让你像操作对象一样操作参数:
方法 | 作用 | 示例 |
get(key) | 获取第一个匹配值 | params.get('id') → "123" |
getAll(key) | 获取所有匹配值(数组) | params.getAll('tags') → ["js","web"] |
has(key) | 是否存在该参数 | params.has('name') → true |
append(key, value) | 追加一个值(允许重复) | params.append('tags','css') |
set(key, value) | 设置或覆盖该参数(删除所有同名的旧值) | params.set('page','2') |
delete(key) | 删除该参数的所有值 | params.delete('name') |
toString() | 序列化为查询字符串(不含 ?) | "id=123&name=追风" |
sort() | 按键名排序(用于缓存或签名) | params.sort() |
forEach() / entries() / keys() / values() | 遍历 | 见下文 |
实战演练:
const params = new URLSearchParams();
params.append('tags', 'js');params.append('tags', 'web');params.append('tags', 'css');
params.set('name', '追风');params.set('page', '1');console.log(params.get('tags')); console.log(params.getAll('tags'));
params.set('page', '2');console.log(params.get('page'));
params.delete('tags');console.log(params.has('tags'));
console.log(params.toString());
for (const [key, value] of params) { console.log(`${key}: ${value}`);}
params.sort();console.log(params.toString());
2.3 为什么别再手写解析?
对比一下,你会立刻明白差别:
传统手写方式(不推荐):
function getParam(name) { const match = location.search.match(new RegExp('[?&]' + name + '=([^&]*)')); return match ? decodeURIComponent(match[1]) : null;}
URL API 方式
const params = new URLSearchParams(location.search);const value = params.get('name'); const all = params.getAll('tags');
简单、可靠、无歧义。这就是标准化的力量。
三、相对路径解析:再也不用纠结 ../ 了
在拼接 URL 时,经常遇到相对路径。URL 构造函数借助 base 参数,可以像浏览器一样解析路径关系:
const base = 'https://example.com/docs/api/';console.log(new URL('overview.html', base).href);
console.log(new URL('../index.html', base).href);
console.log(new URL('/absolute/path', base).href);
console.log(new URL('https://other.com', base).href);
关键细节:base 是否以 / 结尾,会影响拼接行为:
const base1 = 'https://example.com/docs/api'; const base2 = 'https://example.com/docs/api/'; console.log(new URL('overview.html', base1).href); console.log(new URL('overview.html', base2).href);
因此,如果你要拼接到目录下,请确保 base 以 / 结尾。
四、动态构建 URL:像搭积木一样灵活
URL 对象的所有属性(除了只读的 origin)都是可写的。你可以随时修改任意部分,然后通过 href 获取新的完整 URL。
const url = new URL('https://example.com/page');url.protocol = 'http:';url.hostname = 'api.example.com';url.port = '3000';url.pathname = '/v2/users';url.searchParams.set('id', '456');url.hash = '#details';console.log(url.href);
这种“修改即生效”的方式,非常适合在函数中根据参数动态生成 URL:
* 构建 API 请求 URL * @param {string} endpoint - 接口路径,如 "users" * @param {object} params - 查询参数对象 * @param {string} base - 基础域名(可选) */function buildApiUrl(endpoint, params = {}, base = 'https://api.example.com') { const url = new URL(endpoint, base); const searchParams = url.searchParams; Object.entries(params).forEach(([key, value]) => { if (Array.isArray(value)) { value.forEach(v => searchParams.append(key, v)); } else { searchParams.set(key, value); } }); return url.toString();}const result = buildApiUrl('users', { page: 2, limit: 20, keywords: '前端开发', tags: ['js', 'vue']});console.log(result);
这个函数自动处理了编码、数组参数、路径拼接,比用模板字符串拼接安全得多。
五、编码与解码:别再为 %20 头疼了
URL 中有些字符(如空格、&、#)必须转义成百分号编码形式。新手容易忽略,老手也常手滑。而 URL API 的 最大优点之一,就是自动处理编码 。
5.1 自动编码,自动解码
const url = new URL('https://example.com');url.searchParams.set('q', 'a b&c=d#e');console.log(url.search); console.log(url.searchParams.get('q'));
你 完全不需要手动调用 encodeURIComponent,只要通过 searchParams.set/append 赋值,API 内部会做好一切。
5.2 如果需要手动编码
虽然大部分场景不需要,但了解一下也无妨:
encodeURIComponent('追风 & 贪狼'); encodeURI('https://example.com/你好'); decodeURIComponent('%E8%BF%BD%E9%A3%8E');
建议:在 URL 和 URLSearchParams 能覆盖的场景下,尽量别手动编码,既省心又避错。
六、真实场景中的妙用
6.1 获取当前页面的参数
const params = new URLSearchParams(window.location.search);const source = params.get('utm_source'); const medium = params.get('utm_medium');
6.2 在 fetch 请求中拼接参数
async function fetchProducts(filters) { const url = new URL('/api/products', window.location.origin); Object.keys(filters).forEach(key => { if (filters[key] !== undefined && filters[key] !== null) { url.searchParams.set(key, filters[key]); } }); const response = await fetch(url); return response.json();}fetchProducts({ category: 'books', minPrice: 20, maxPrice: 100 });
6.3 表单数据一键转查询参数
const form = document.querySelector('#search-form');form.addEventListener('submit', (e) => { e.preventDefault(); const formData = new FormData(form); const params = new URLSearchParams(formData); window.location.search = params.toString(); });
6.4 生成签名 URL(防篡改)
很多接口需要在参数后附加签名,要求参数必须按字母序排列,以保证签名一致性:
function signUrl(baseUrl, params, secretKey) { const url = new URL(baseUrl); Object.entries(params).forEach(([k, v]) => url.searchParams.set(k, v)); url.searchParams.sort(); const signature = hmacSHA256(secretKey, url.searchParams.toString()); url.searchParams.set('sign', signature); return url.toString();}
这里的 sort() 方法让参数顺序固定,是签名前的关键一步。
七、两个静态方法:对象 URL 与合法性检测
7.1 URL.createObjectURL() —— 为 Blob/File 创建临时链接
当你需要预览用户上传的图片、下载动态生成的文件时,这个 API 非常有用:
const input = document.querySelector('input[type="file"]');input.addEventListener('change', (e) => { const file = e.target.files[0]; if (file) { const objectURL = URL.createObjectURL(file); document.querySelector('#preview').src = objectURL; }});
createObjectURL 生成的 URL 格式类似 blob:https://example.com/xxxxx-xxxx,它指向浏览器内存中的对象。用完一定要调用 revokeObjectURL 释放资源,尤其是在大量文件预览的场景下。
7.2 URL.canParse() —— 安全检测(较新)
在尝试 new URL() 之前,先判断字符串是否合法,可以避免抛出异常:
function safeParse(input, base) { if (URL.canParse(input, base)) { return new URL(input, base); } return null; }const userInput = 'https://example.com';const url = safeParse(userInput);if (url) { } else { alert('您输入的网址不合法');}
兼容性提示:canParse 在 2023 年后的浏览器(Chrome 120+、Firefox 115+、Safari 17+)中支持,Node.js 22+ 也支持。如果兼容性要求高,可以自己用 try...catch 替代。
八、避坑指南与最佳实践
推荐做法:
1. 使用 URL 构造,不用字符串拼接
new URL('/api', base) 比 base + '/api' 可靠得多。
2. 通过 searchParams 操作参数,不手写正则
自动编码、处理重复值、排序,这些都是手写难以完美实现的。
3. 记住 origin 是只读的
想改协议或主机?直接改 protocol 和 hostname,不能改 origin。
4. 对象 URL 及时释放
每次 createObjectURL 都要配对 revokeObjectURL,尤其在 SPA 中注意组件卸载时清理。
5. 善用 sort() 确保参数顺序
对缓存键生成或签名计算很有帮助。
常见错误
new URL('path', 'relative'); new URL('path', 'https://example.com');
const url = 'https://example.com?q=' + 'a b&c';const url = new URL('https://example.com');url.searchParams.set('q', 'a b&c');
url.search = '?a=1'; url.searchParams.set('a', '1');
兼容性一览
URL 和 URLSearchParams:所有现代浏览器(Chrome、Firefox、Safari、Edge)均支持,IE 不支持。
Node.js:从 v10 开始全局可用,无需额外引入。
URL.canParse():较新,不支持时建议用 try...catch 回退。
总结
核心工具 | 主要职责 |
new URL() | 解析、修改、拼接 URL 的各个部分 |
url.searchParams | 对查询参数进行增删改查、遍历、排序 |
URL.createObjectURL() | 为前端生成的 Blob/File 创建可访问的临时链接 |
URL.canParse() | 安全地预检 URL 字符串是否合法 |
一句话记住:凡是需要和 URL 打交道的地方,就优先想到 URL 和 URLSearchParams——它们能让你写出更干净、更安全、更不易出错的代码。
该文章在 2026/7/1 14:41:51 编辑过