解析URL的参数为对象,是面试中被高频问到的手写题,当我按照正常书写时候,面试官问到,你知道你这里还有一些情况你没有处理到吗? 是什么呢? 接下来一起看看吧
一般情况下,我们在不考虑URL参数的特殊情况下,
function queryString(url) {
const paramsStr = /.+\?(.+)$/.exec(url)?.[1]; // 将 ? 后面的字符串取出来
const paramsArr = paramsStr.split('&'); // 将字符串以 & 分割后存到数组中
let paramsObj = {};
// 将 params 存到对象中
paramsArr.forEach(param => {
if (/=/.test(param)) { // 处理有 value 的参数
let [key, val] = param.split('='); // 分割 key 和 value
val = decodeURIComponent(val); // 解码,兼容URL中字符已经被编码的情况
val = /^\d+$/.test(val) ? parseFloat(val) : val; // 判断是否转为数字
if (paramsObj.hasOwnProperty(key)) { // 如果对象有 key,则添加一个值
paramsObj[key] = [].concat(paramsObj[key], val);
} else { // 如果对象没有这个 key,创建 key 并设置值
paramsObj[key] = val;
}
} else { // 处理没有 value 的参数
paramsObj[param] = true;
}
})
return paramsObj;
}
测试代码
console.log(queryString('https://www.baidu.com?a=16&b=2c=5&f=jello'))
console.log(queryString('https://www.baidu.com?color=deep%20blue'))
以上解法对一般情况是OK的
问题点1: 未设定值的key会被赋值为true
当我们用以下测试用例会发现:
console.log(queryString('https://www.baidu.com?a=16&&8&b=2c=5&f=jello'))
可以看到,key为8的值被赋予了true ,这种情况,我们可以对它进行过滤
通过[]设置的,属性值没有被解构处理
再来看一种情况,console.log(queryString('https://www.baidu.com?a[name]=f // console.log(queryString('https://www.baidu.com?a[name]=f&a[com]=teb&b=why')) 输出为:
从上图可以看到,似乎有点不是很对,正常情况下,应该被解析为:{ a: { name: 'f', com: 'teb' }, b: 'why' },这里出现原因是我们没有处理对象通过[]访问属性的情况:如a[name]=f&a[com]=teb&b=why 。这里的功能,类似于lodash 的get 方法,
手写lodash get方法
// 实现lodash的get, 例如
const obj = { weight: [{ a: 1 }, { b: 2 }], height: 5, attr: { c: 'smart' }}
// lodashGet(obj,'weight[1].b') 输出2
// lodashGet(obj,'height') 输出5
// lodashGet(obj, 'attr.c') 输出 smart
// lodashGet(obj, 'attr.c.d') 输出 undefined
function lodashGet(data, path,defaultValue={}){
let regPath = path.replace(/\[(\d+)\]/g, '.$1')
.split('.')
let result = data;
for(let path of regPath){
result = Object(result)[path]
if(result == null){
return defaultValue
}
}
return result
}
console.log(lodashGet(obj,'height'))
所以,要解决上面的问题,可以按照lodash.get 思路来解决
最终版本code
function queryString(url){
let str = /.+\?(.+)$/.exec(url)?.[1]
return str.split('&').reduce((prev, cur) => {
const [key, value] = cur.split('=')
if(!value){
return prev
}
deepSet(prev, key.split(/[\[\]]/g).filter(x => x), value);
return prev
},{})
}
function deepSet(obj, path, value){
let i = 0;
for(; i < path.length - 1; i++){
if(obj[path[i]] == undefined){
if(path[i+1].match(/^\d+$/)){
obj[path[i]] = []
}else {
obj[path[i]] = {}
}
}
obj = obj[path[i]]
}
obj[path[i]] = decodeURIComponent(value)
}
console.log(queryString('https://www.baidu.com?a=16&&8&b=2c=5&f=jello'))
console.log(queryString('https://www.baidu.com?a=16&b=2c=5&f=jello'))
console.log(queryString('https://www.baidu.com?a&b&c'))
console.log(queryString('https://www.baidu.com?a[name]=f&a[com]=teb&b=hly'))
console.log(queryString('https://www.baidu.com?color=deep%20blue'))
注意点
- 书写的过程,主要非空判断
- 对传进来的url进行一层解码
- 对参数连续的&进行处理
- 对参数是通过
[]设置的情况进行兼容处理
至此,就介绍结束了,如果有更好的意见,欢迎指出
开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 1 天 点击查看详情