JS如何解析URL的参数?
JavaScript如何解析URL的参数?
URL 解析 🔗
在3.0版本的Router中,有两个核心的函数来处理URL,如下:
parseURL预处理整个URL地址,然后把参数部分传给下面这个函数来处理;parseQuery处理URL中的参数部分,返回一个对象。
可以自己把仓库拉下来,这样看起来更方便
parseURL 🔗
找到src下面的location.ts:
打开找到里面的parseURL:
完整的ts代码如下:
export function parseURL(
parseQuery: (search: string) => LocationQuery,
location: string,
currentLocation: string = "/"
): LocationNormalized {
let path: string | undefined,
query: LocationQuery = {},
searchString = "",
hash = "";
// Could use URL and URLSearchParams but IE 11 doesn't support it
const searchPos = location.indexOf("?");
const hashPos = location.indexOf("#", searchPos > -1 ? searchPos : 0);
if (searchPos > -1) {
path = location.slice(0, searchPos);
searchString = location.slice(
searchPos + 1,
hashPos > -1 ? hashPos : location.length
);
query = parseQuery(searchString);
}
if (hashPos > -1) {
path = path || location.slice(0, hashPos);
// keep the # character
hash = location.slice(hashPos, location.length);
}
// no search and no query
path = resolveRelativePath(path != null ? path : location, currentLocation);
// empty path means a relative query or hash `?foo=f`, `#thing`
return {
fullPath: path + (searchString && "?") + searchString + hash,
path,
query,
hash,
};
}由于只关注如何解析参数,所以这个函数有些地方忽略。
先看看参数:
parseQuery一个解析参数的函数,也就是使得解析变成可配置的,下文的parseQuery为一个实现;location一个网址,比如这个帖子的地址:http://localhost:4000/2020/10/18/JS%E5%A6%82%E4%BD%95%E8%A7%A3%E6%9E%90URL%E7%9A%84%E5%8F%82%E6%95%B0%EF%BC%9F/currentLocation和解析过程无关,不用在意它的意思。
const searchPos = location.indexOf("?");
const hashPos = location.indexOf("#", searchPos > -1 ? searchPos : 0);先判断了网址location中?的索引以及#的索引,#的索引从?之后开始找。
#之后的字符(包括本身)都不应该作为地址查询参数的一部分。
比如?id=1001&name=#lwf,这里的地址查询参数应该只有?id=1001&name=,而不是?id=1001&name=#lwf。
if (searchPos > -1) {
path = location.slice(0, searchPos);
searchString = location.slice(
searchPos + 1,
hashPos > -1 ? hashPos : location.length
);
query = parseQuery(searchString);
}如果存在?,那么可能存在参数(因为可能就只有一个?存在,此时查询参数就为空)。
path = location.slice(0, searchPos);截取?前面的部分,和解析没什么关系,先不用管。
searchString = location.slice(
searchPos + 1,
hashPos > -1 ? hashPos : location.length
);把?(不包括本身,因为此时起始索引为searchPos + 1)和#(如果存在,不包括本身,不存在,截取到地址末尾)之间的字符串截取出来。
然后执行parseQuery函数,下面的过程和解析参数就没什么关系了,可以接着看parseQuery函数。
parseQuery 🔗
找到src下面的query.ts:
然后打开找到里面的parseQuery函数,就是URL地址参数解析的核心函数。
完整的ts代码如下:
export function parseQuery(search: string): LocationQuery {
const query: LocationQuery = {};
// avoid creating an object with an empty key and empty value
// because of split('&')
if (search === "" || search === "?") return query;
const hasLeadingIM = search[0] === "?";
const searchParams = (hasLeadingIM ? search.slice(1) : search).split("&");
for (let i = 0; i < searchParams.length; ++i) {
const searchParam = searchParams[i];
// allow the = character
let eqPos = searchParam.indexOf("=");
let key = decode(eqPos < 0 ? searchParam : searchParam.slice(0, eqPos));
let value = eqPos < 0 ? null : decode(searchParam.slice(eqPos + 1));
if (key in query) {
// an extra variable for ts types
let currentValue = query[key];
if (!Array.isArray(currentValue)) {
currentValue = query[key] = [currentValue];
}
currentValue.push(value);
} else {
query[key] = value;
}
}
return query;
}可以先看它的输入参数
search需要解析的用于搜索字符串,比如?id=1&name=lwf这种的
返回的参数为类型为LocationQuery的一个对象,可以找到它的定义:
export type LocationQuery = Record<
string,
LocationQueryValue | LocationQueryValue[]
>;
export type LocationQueryValue = string | null;也就是返回一个普通的字面对象,不过属性的值为字符串或者null或者是这两者组成的数组
if (search === "" || search === "?") return query;首先判断了传入字符串的特殊情况,空字符串和单个问号不用继续运行下去,直接返回空的对象即可。
在这句话的上面有一行注释:
avoid creating an object with an empty key and empty value because of split('&')
意思是这个判断为了避免由于split('&')产生键名为空键值也为空的查询参数的情况,这是什么意思呢?
先不急,我们接着往下看。
const hasLeadingIM = search[0] === "?";
const searchParams = (hasLeadingIM ? search.slice(1) : search).split("&");判断了开头是否为?(内置方法有startWith也可以判断起始字符串)。
然后如果存在就去掉这个?,也就是执行search.slice(1)。
然后以&分割字符串,比如现在传入了?id=1001&name=lwf&age=13。
运行到这里searchParams的值为['id=1001','name=lwf','age=13']。
for (let i = 0; i < searchParams.length; ++i) {
// ...
}接下来是对searchParams的一个循环:
const searchParam = searchParams[i];
let eqPos = searchParam.indexOf("=");searchParam变量没啥好说的,就是searchParams数组循环的当前值。
eqPos确定了每个字符串中=的位置,如果不存在,为-1,比如前面的id=1001,那么此时eqPos为2。
如果只想单纯判断目标字符串是否存在源字符串中,使用字符串的includes方法会更好(返回true或者false),这里使用indexOf是因为返回的索引之后要用到。
let key = decode(eqPos < 0 ? searchParam : searchParam.slice(0, eqPos));
let value = eqPos < 0 ? null : decode(searchParam.slice(eqPos + 1));eqPos小于0,也就是出现了某些没有值的属性名,比如?id&name=lwf,这里的id就没有对应的值。
如果eqPos大于0(即存在=),那么把字符串分割成两部分。。
searchParam.slice(0,eqPos)分割了key的部分(slice的第二个参数为需要分割的末尾,开区间,也就是不包括这个索引);searchParam.slice(eqPos + 1)分割了value的部分,(slice第二个参数默认字符串的长度,也就是截取到末尾)。
然后通过一个decode来对数据进行处理然后使用,decode函数如下:
export function decode(text: string | number): string {
try {
return decodeURIComponent("" + text);
} catch (err) {
__DEV__ && warn(`Error decoding "${text}". Using original value`);
}
return "" + text;
}decodeURIComponent是浏览器内置的一个函数,用于还原已经编码过的字符串。
比如(空格)会被编码成%20。
这个帖子的地址:/2020/10/18/JS如何解析URL的参数?/
会被编码成:/2020/10/18/JS%E5%A6%82%E4%BD%95%E8%A7%A3%E6%9E%90URL%E7%9A%84%E5%8F%82%E6%95%B0%EF%BC%9F/。
decodeURIComponent函数作用就是还原成未编码前,MDN上有详细讲编码这块的:
decodeURIComponent() - MDN web docs > encodeURIComponent() - MDN web docs
decode尝试还原编码后的字符串,如果失败就返回原来的字符串。
那么在这两步之后,比如'id=1001'就会变成key='id'以及value='1001'。
而比如'id'这种,就会变成key='id'以及value=null。
当然,此时还会出现一种情况,就是?=id&name=lwf,此时slice(0, 0)返回了一个空字符串。
也就是此时变成key=''而value='id',但是这没有关系,因为字面对象支持以空字符串作为属性名,如下图:
if (key in query) {
// an extra variable for ts types
let currentValue = query[key];
if (!Array.isArray(currentValue)) {
currentValue = query[key] = [currentValue];
}
currentValue.push(value);
} else {
query[key] = value;
}接下来是一个if-else语句,判断了当前的key在不在结果集query中。
为什么要检测,因为某些时候可能出现同名,但是不同值的情况。
比如?id=1001&name=lwf&name=ghost。
这时键名为name有两个值:lwf和ghost,处理办法就是以一个数组来存储结果集。
在键名存在的时候,通过判断来使得该键名对应的值转为数组然后把当前的值存进去。
let currentValue = query[key];
if (!Array.isArray(currentValue)) {
// 不是数组先转成一个数组
currentValue = query[key] = [currentValue];
}
// 把当前值push进去
currentValue.push(value);不过在应对相同键名相同键值时,会使得数组存入相同的值,感觉可以去提个PR了 🤣。
query[key] = value;不存在相同键名情况下,直接以该值作为键值即可。
回到之前那个注释,产生键名为空键值也为空的查询参数是如何发生的呢?
如果search为'',那么在split('&')执行之后,结果为['']
接着key和value的产生,由于找不到=,key就为'',而value就为null,
返回的参数就变成了
query = {
"": null,
};这是一个没有意义的参数对象,所以加了判断避免了这种情况
return query;最后就是返回构建出来的query对象
后记 🔗
总结起来有几个要点:
- 地址分割,
?和#之间的字符串; =是否存在;- 多
value处理; - 解码编码。





