1.起因
事情的起因是在前端交流群里,有人询问这样一个问题,要求实现这个parseUrl函数。
const url = 'https://www.gaotu.cn?user=gsx&user=gt&user=王麻子&id=111&name=%E5%BC%A0%E4%B8%89';
console.log(parseUrl(url));
// 输出:{ user: [ 'gsx', 'gt', '王麻子' ], id: '111', name: '张三' }
我最开始想到的实现方法是通过字符串的分割,划分成数组,在一步一步拼接成最后的数据。
- 最开始的思路,就能得到想要的结果
const url = 'https://www.gaotu.cn?user=gsx&user=gt&user=王麻子&id=111&name=%E5%BC%A0%E4%B8%89';
function parseUrl (url) {
const arr1 = url.split('&')
let arr2 = arr1[0].split('?')
arr1[0] = arr2
let arr3 = arr1.flat()
let arr4 = []
let obj = {}
let obj1 = {}
let obj2 = {}
for (let value of arr3) {
if (value.split('=')[0] === 'user') {
arr4.push(value.split('=')[1])
} else if (value.split('=')[0] === 'id') {
obj1.id = value.split('=')[1]
} else if (value.split('=')[0] === 'name') {
obj2.name = decodeURI(value.split('=')[1])
}
}
obj.user = arr4
return Object.assign(obj, obj1, obj2)
}
- 后面发现群友的方法更优雅,实现也相对容易。涉及了
new url()的相关知识。所以总结一个笔记,方便以后学习。
2. JS 中的new url
URL接口用于解析,构造,规范化和编码 URL。它通过提供允许你轻松阅读和修改 URL 组件的属性来工作。- 通常,通过在调用 URL 的构造函数时将 URL 指定为字符串或提供相对 URL 和基本 URL 来创建新的 URL 对象。
2.1 new URL()
创建并返回一个URL对象,该 URL 对象引用使用绝对 URL 字符串,相对 URL 字符串和基本 URL 字符串指定的 URL。
hash: "" ,//其中包含域(即主机名),后跟(如果指定了端口)“:”和 URL 的端口。
host: "www.gaotu.cn"//其中包含域(即主机名),后跟(如果指定了端口)“:”和 URL 的端口。
hostname: "www.gaotu.cn"//URL域名
href: "https://www.gaotu.cn/?user=gsx&user=gt&user=%E7%8E%8B%E9%BA%BB%E5%AD%90&id=111&name=%E5%BC%A0%E4%B8%89"//完整的url
origin: "https://www.gaotu.cn"//返回一个包含协议名
域名、端口号
password: "",//包含在域名前面指定的密码
pathname: "/",//以 '/' 起头紧跟着 URL 文件路径
port: "",//包含 URL 端口号
protocol: "https:",//包含 URL 协议名
//指示 URL 的参数字符串;如果提供了任何参数,则此字符串包括所有参数,并以开头的“?”开头 字符。
search: "?user=gsx&user=gt&user=%E7%8E%8B%E9%BA%BB%E5%AD%90&id=111&name=%E5%BC%A0%E4%B8%89",
searchParams: URLSearchParams {size: 5},//对象,可用于访问`search`中找到的各个查询参数
username: "",//包含在域名前面指定的用户名
new URL(url, base)
- url 一个表示绝对或相对 URL 的
DOMString或任何具有字符串化方法的对象,例如<a>或<area>元素。如果url是相对 URL,则会将base用作基准 URL。如果url是绝对 URL,则无论参数base是否存在,都将被忽略。 base可选。 一个表示基准 URL 的字符串,当url为相对 URL 时,它才会生效。如果未指定,它默认为undefined。
2.2 URLSearchParams
接口可用于构建和处理 URL 查询字符串。要从当前窗口的 URL 获取搜索参数,可以执行以下操作
- 构造函数
URLSearchParams()返回一个URLSearchParams对象。
- 实例属性
size只读 返回URLSearchParams对象中查询参数的总个数。
- 实例方法
-
URLSearchParams.[@@iterator]()- 返回一个
iterator,允许以键/值对在查询字符串中出现的顺序迭代包含在该对象的键/值对。
- 返回一个
-
URLSearchParams.append()插入一个指定的键/值对作为新的查询参数。 -
URLSearchParams.delete()从查询参数列表里删除指定的查询参数及其对应的值。 -
URLSearchParams.entries()返回一个iterator可以遍历所有键/值对的对象。 -
URLSearchParams.forEach()通过回调函数迭代此对象中包含的所有值。 -
URLSearchParams.get()获取指定查询参数的第一个值。 -
URLSearchParams.getAll()获取指定查询参数的所有值,返回是一个数组。 -
URLSearchParams.has()返回Boolean判断是否存在此查询参数。 -
URLSearchParams.keys()返回iterator此对象包含了键/值对的所有键名。 -
URLSearchParams.set()设置一个查询参数的新值,假如原来有多个值将删除其他所有的值。 -
URLSearchParams.sort()按键名排序。 -
URLSearchParams.toString()返回查询参数组成的字符串,可直接使用在 URL 上。 -
URLSearchParams.values()返回iterator此对象包含了键/值对的所有值。
const paramsString = "q=URLUtils.searchParams&topic=api";
const searchParams = new URLSearchParams(paramsString);
// 迭代查询参数
for (let p of searchParams) {
console.log(p);
}
const urlParams = new URL(window.location.href);//将字符串变成url对象
urlParams.searchParams.has("topic") === true; // true //判断url是否存在这个key值
urlParams.searchParams.get("topic") === "api"; // true //获取这个key值的value
urlParams.searchParams.getAll("topic"); // ["api"] //获取url所有的key
urlParams.searchParams.get("foo") === ""; // true //key值为空的情况
urlParams.searchParams.append("topic", "webdev"); //url追加一个key-value
urlParams.searchParams.toString(); // "q=URLUtils.searchParams&topic=api&topic=webdev"
//将参数对象转为字符串并且进行了拼接
urlParams.searchParams.set("topic", "More webdev"); //设定key-value
urlParams.searchParams.toString(); // "q=URLUtils.searchParams&topic=More+webdev"
urlParams.searchParams.delete("topic"); //删除key-value
urlParams.searchParams.toString(); // "q=URLUtils.searchParams"
3. 解决方法
这个parseUrl函数的实现思路如下:
- 将url按问号分割,获取查询参数部分。
- 使用URLSearchParams类解析查询参数部分。
- 遍历所有查询参数的键值对,如果该参数已经存在,则将其转换成数组;否则,存储单个值。
- 返回解析结果。
const url = 'https://www.gaotu.cn?user=gsx&user=gt&user=王麻子&id=111&name=%E5%BC%A0%E4%B8%89';
function parseUrl (url) {
const params = {};
const searchParams = new URLSearchParams(url.split('?')[1]); // 获取查询参数部分
for (const [key, value] of searchParams.entries()) {
if (params[key]) { // 如果该参数已经存在,则将其转换成数组
if (Array.isArray(params[key])) {
params[key].push(decodeURIComponent(value)); // 对数组进行追加
} else {
params[key] = [params[key], decodeURIComponent(value)]; // 转换为数组
}
} else {
params[key] = decodeURIComponent(value); // 存储单个值
}
}
return params;
}
console.log('parseUrl', parseUrl(url));
// { user: [ 'gsx', 'gt', '王麻子' ], id: '111', name: '张三' }
其他
decodeURIComponent介绍:用于解码由 encodeURIComponent方法或者其他类似方法编码的部分统一资源标识符(URI)。
decodeURI()函数可对encodeURI()函数编码过的 URI 进行解码。decodeURIComponent()函数可对encodeURIComponent()函数编码的 URI 进行解码。
从W3C的定义和用法来看,两者没有什么区别,但是两者的参数是有区别的:
decodeURI(URIstring)
//URIstring 一个字符串,含有要解码的 URI 或其他要解码的文本。
decodeURIComponent(URIstring)
//URIstring 一个字符串,含有编码 URI 组件或其他要解码的文本。
区别:
encodeURIComponent和decodeURIComponent可以编码和解码URI特殊字符(如#,/,¥等),- 而
decodeURI则不能。
encodeURIComponent('#')
"%23"
decodeURI('%23')
"%23"
decodeURIComponent('%23')
"#"
encodeURI('#')
"#"