面试官说《解析 URL Params 为对象--queryString》这些点你注意到了吗

116 阅读2分钟

解析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的

image.png

image.png

问题点1: 未设定值的key会被赋值为true

当我们用以下测试用例会发现:

console.log(queryString('https://www.baidu.com?a=16&&8&b=2c=5&f=jello'))

image.png

image.png

可以看到,key为8的值被赋予了true ,这种情况,我们可以对它进行过滤

image.png

通过[]设置的,属性值没有被解构处理

再来看一种情况,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')) 输出为: image.png

从上图可以看到,似乎有点不是很对,正常情况下,应该被解析为:{ a: { name: 'f', com: 'teb' }, b: 'why' },这里出现原因是我们没有处理对象通过[]访问属性的情况:如a[name]=f&a[com]=teb&b=why 。这里的功能,类似于lodashget 方法,

image.png

手写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'))

image.png

注意点

  1. 书写的过程,主要非空判断
  2. 对传进来的url进行一层解码
  3. 对参数连续的&进行处理
  4. 对参数是通过[]设置的情况进行兼容处理

至此,就介绍结束了,如果有更好的意见,欢迎指出

开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 1 天 点击查看详情