JS如何解析URL的参数?
JavaScript
如何解析URL
的参数?
URL 解析
在3.0
版本的Router
中,有两个核心的函数来处理URL
,如下:
parseURL
预处理整个URL
地址,然后把参数部分传给下面这个函数来处理;parseQuery
处理URL
中的参数部分,返回一个对象。
可以自己把仓库拉下来,这样看起来更方便
parseURL
找到src
下面的location.ts
:
打开找到里面的parseURL
:
完整的ts
代码如下:
1 | export function parseURL( |
由于只关注如何解析参数,所以这个函数有些地方忽略。
先看看参数:
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
和解析过程无关,不用在意它的意思。
1 | const searchPos = location.indexOf("?"); |
先判断了网址location
中?
的索引以及#
的索引,#
的索引从?
之后开始找。
#
之后的字符(包括本身)都不应该作为地址查询参数的一部分。
比如?id=1001&name=#lwf
,这里的地址查询参数应该只有?id=1001&name=
,而不是?id=1001&name=#lwf
。
1 | if (searchPos > -1) { |
如果存在?
,那么可能存在参数(因为可能就只有一个?
存在,此时查询参数就为空)。
1 | path = location.slice(0, searchPos); |
截取?
前面的部分,和解析没什么关系,先不用管。
1 | searchString = location.slice( |
把?
(不包括本身,因为此时起始索引为searchPos + 1
)和#
(如果存在,不包括本身,不存在,截取到地址末尾)之间的字符串截取出来。
然后执行parseQuery
函数,下面的过程和解析参数就没什么关系了,可以接着看parseQuery
函数。
parseQuery
找到src
下面的query.ts
:
然后打开找到里面的parseQuery
函数,就是URL
地址参数解析的核心函数。
完整的ts
代码如下:
1 | export function parseQuery(search: string): LocationQuery { |
可以先看它的输入参数
search
需要解析的用于搜索字符串,比如?id=1&name=lwf
这种的
返回的参数为类型为LocationQuery
的一个对象,可以找到它的定义:
1 | export type LocationQuery = Record< |
也就是返回一个普通的字面对象,不过属性的值为字符串或者null
或者是这两者组成的数组
1 | if (search === "" || search === "?") return query; |
首先判断了传入字符串的特殊情况,空字符串和单个问号不用继续运行下去,直接返回空的对象即可。
在这句话的上面有一行注释:
avoid creating an object with an empty key and empty value because of
split('&')
意思是这个判断为了避免由于split('&')
产生键名为空键值也为空的查询参数的情况,这是什么意思呢?
先不急,我们接着往下看。
1 | const hasLeadingIM = search[0] === "?"; |
判断了开头是否为?
(内置方法有startWith
也可以判断起始字符串)。
然后如果存在就去掉这个?
,也就是执行search.slice(1)
。
然后以&
分割字符串,比如现在传入了?id=1001&name=lwf&age=13
。
运行到这里searchParams
的值为['id=1001','name=lwf','age=13']
。
1 | for (let i = 0; i < searchParams.length; ++i) { |
接下来是对searchParams
的一个循环:
1 | const searchParam = searchParams[i]; |
searchParam
变量没啥好说的,就是searchParams
数组循环的当前值。
eqPos
确定了每个字符串中=
的位置,如果不存在,为-1
,比如前面的id=1001
,那么此时eqPos
为2
。
如果只想单纯判断目标字符串是否存在源字符串中,使用字符串的includes
方法会更好(返回true
或者false
),这里使用indexOf
是因为返回的索引之后要用到。
1 | let key = decode(eqPos < 0 ? searchParam : searchParam.slice(0, eqPos)); |
eqPos
小于0
,也就是出现了某些没有值的属性名,比如?id&name=lwf
,这里的id
就没有对应的值。
如果eqPos
大于0
(即存在=
),那么把字符串分割成两部分。。
searchParam.slice(0,eqPos)
分割了key
的部分(slice
的第二个参数为需要分割的末尾,开区间,也就是不包括这个索引);searchParam.slice(eqPos + 1)
分割了value
的部分,(slice
第二个参数默认字符串的长度,也就是截取到末尾)。
然后通过一个decode
来对数据进行处理然后使用,decode
函数如下:
1 | export function decode(text: string | number): string { |
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'
,但是这没有关系,因为字面对象支持以空字符串作为属性名,如下图:
1 | if (key in query) { |
接下来是一个if-else
语句,判断了当前的key
在不在结果集query
中。
为什么要检测,因为某些时候可能出现同名,但是不同值的情况。
比如?id=1001&name=lwf&name=ghost
。
这时键名为name
有两个值:lwf
和ghost
,处理办法就是以一个数组来存储结果集。
在键名存在的时候,通过判断来使得该键名对应的值转为数组然后把当前的值存进去。
1 | let currentValue = query[key]; |
不过在应对相同键名相同键值时,会使得数组存入相同的值,感觉可以去提个PR
了 🤣。
1 | query[key] = value; |
不存在相同键名情况下,直接以该值作为键值即可。
回到之前那个注释,产生键名为空键值也为空的查询参数是如何发生的呢?
如果search
为''
,那么在split('&')
执行之后,结果为['']
接着key
和value
的产生,由于找不到=
,key
就为''
,而value
就为null
,
返回的参数就变成了
1 | query = { |
这是一个没有意义的参数对象,所以加了判断避免了这种情况
1 | return query; |
最后就是返回构建出来的query
对象
后记
总结起来有几个要点:
- 地址分割,
?
和#
之间的字符串; =
是否存在;- 多
value
处理; - 解码编码。