出处:掘金

原作者:ErpanOmer


我在 Code Review 里最怕看到的代码之一,就是手写正则去解析 URL 参数

每次看到类似下面这样的代码,我的血压就忍不住要升高:

// 一个试图从 URL 里获取'id'参数的函数
function getIdFromUrl(url) {
  const match = url.match(/[?&]id=([^&]*)/);
  return match ? match[1] : null;
}

或者这种 split 链式调用:

const url = 'https://example.com?a=1&b=2';
const paramsStr = url.split('?')[1];
// ...然后再对 paramsStr 按'&'和'='去分割...

这段代码,乍一看好像能用。但作为工程师,我们得考虑边界情况:

  • 它能处理 URL 编码的字符吗?比如 name=%E5%BC%A0%E4%B8%89
  • 它能处理同一个参数出现多次的情况吗?比如 tags=js&tags=css
  • 如果参数在 hash 后面,它能正确处理吗?

答案是,都不能。手写正则和 split 来处理 URL,是一种极其脆弱和危险的写法

其实,浏览器早就给我们提供了一套官方的、功能强大且极其健壮的工具来处理 URL,那就是 URLURLSearchParams 这两个 Web API。今天,我就想彻底聊聊它们

URL 对象:URL 的结构化表示

别再把 URL 当成一个简单的字符串了。我们可以用 new URL() 构造函数,把它变成一个清晰、易于操作的结构化对象

const urlString = 'https://juejin.cn/user/12345/posts?type=hot&page=2#comments';
 
// 传入一个URL字符串,或者一个相对路径 + base URL
const myUrl = new URL(urlString);
 
console.log(myUrl);

一旦你把它变成了 URL 对象,获取它的各个部分就变得极其简单:

console.log(myUrl.protocol); // "https:"
console.log(myUrl.hostname); // "juejin.cn"
console.log(myUrl.port);     // "" (因为URL中没有指定)
console.log(myUrl.pathname); // "/user/12345/posts"
console.log(myUrl.search);   // "?type=hot&page=2"
console.log(myUrl.hash);     // "#comments"

它不仅能读,还能写。修改 URL 的任何一部分,都像修改一个普通的 JS 对象属性一样简单:

myUrl.pathname = '/user/98765/articles';
myUrl.hash = '#profile';
myUrl.port = '8080';
 
// .href 属性会返回拼接好的完整 URL 字符串
console.log(myUrl.href); 
// "https://juejin.cn:8080/user/98765/articles?type=hot&page=2#profile"

看到没?所有部分都被清晰地解析出来了,你可以像操作普通 JS 对象一样去读写,完全不用担心拼接字符串时漏掉 / 或者 ?

URLSearchParams:查询参数的进价用法

URL 对象本身已经很强大了,而它里面还藏着一个宝藏属性:myUrl.searchParams

这个 searchParams 就是一个 URLSearchParams 的实例,它专门用来处理 ?key=value&key2=value2 这部分。我们也可以单独创建它

const paramsString = 'type=hot&category=frontend&tags=react&tags=vue';
const searchParams = new URLSearchParams(paramsString);

有了它,所有关于查询参数的操作,都有了清晰、标准的方法:

1 . 读取参数 .get()

console.log(searchParams.get('type')); // "hot" console.log(searchParams.get('nonexistent')); // null

2. 检查参数是否存在 .has()

console.log(searchParams.has('category')); // true

3. 处理同名参数 .getAll()

这是手写正则最头疼的地方。URLSearchParams 能完美处理

console.log(searchParams.getAll('tags')); // ["react", "vue"]

4. 修改与添加参数 .set() & .append()

// .set() 会覆盖已有的值
searchParams.set('type', 'new'); 
 
// .append() 会在已有的值后面追加,而不会覆盖
searchParams.append('tags', 'css');

5. 删除参数 .delete()

searchParams.delete('category');

6. 迭代参数 .forEach() 或 for...of

for (const [key, value] of searchParams) {
  console.log(`${key}: ${value}`);
}

7. 转换回字符串 .toString()

这是最重要的一步。它会自动帮你处理好 URL 编码

// 假设我们添加一个带中文的参数
searchParams.set('author', '张三');
 
console.log(searchParams.toString());
// "type=new&tags=react&tags=vue&tags=css&author=%E5%BC%A0%E4%B8%89"

看到 %E5%BC%A0%E4%B8%89 了吗?它自动帮你把“张三”给 URL 编码了。这一切,如果你用正则去实现,代码量至少多五倍,而且还全是 bug