3

相信很多人看到这个题就觉得太easy了,不就是解析url参数吗?split一把梭不就行了?别急 往下看。

现在有如下的一个url,请你解析出GET参数
let url = "http://www.xxx.com?a=1&b=2&c=3"
// 也就是获取到: {a: 1, b: 2, c: 3}

你可能瞬间写出来:

最基础版本 split一把梭
function parse(url) {
  let obj = {};
  url
    .slice(url.indexOf("?") + 1)
    .split("&")
    .map(item => {
      let [key, val] = item.split("=");
      obj[key] = val;
    });
  return obj;
}

let result = parse(url);
console.log(result);
// { a: '1', b: '2', c: '3' }

ok, 这是作为一名前端必须掌握的最基础的知识点,能写出来不代表你多厉害,但是如果上面这个方法你写不出来,去面壁思过吧?
接着往下继续深问:
上面使用map,你可以用reduce实现吗?能写出来说明你的基础还算可以。

基础版本 reduce
function parse(url) {
  return url
    .slice(url.indexOf("?") + 1)
    .split("&")
    .reduce((prev, curr) => {
      let [key, val] = curr.split("=");
      prev[key] = val;
      return prev;
    }, {});
}

let result = parse(url);
console.log(result);
// { a: '1', b: '2', c: '3' }

ok,上方都是基础的开胃菜,下方正文开始

坑1 没有value
如果参数长这样子怎么搞?
let url = "http://www.xxx.com?a&b&c"
如果用上方方法解析会返回如下:
{ a: undefined, b: undefined, c: undefined, d: undefined }
不难,加个val的判断即可
function parse(url) {
  return url
    .slice(url.indexOf("?") + 1)
    .split("&")
    .reduce((prev, curr) => {
      let [key, val] = curr.split("=");
      if (!val) return prev; // val 为空情况
      prev[key] = val;
      return prev;
    }, {});
}

let result = parse(url);
console.log(result);
// {}
坑2 对象的querystring
如果参数长这样子怎么搞?
let url = "http://www.xxx.com?a[name]=tiger&a[age]=25&b[name]=cat&c=666"
这个就比较恶心了,单独将这一坨给拎出来:
a[name]=tiger&a[age]=25&b[name]=cat&c=666
其实就是解析成如下的一个对象:
{
    a:{
        name:'tiger',
        age:25
    },
    b:{
        name:'cat'
    },
    c:666
}
如果还用上方方法会解析成如下:
{ 'a[name]': 'tiger', 'a[age]': '25', 'b[name]': 'cat', c: '666' }
明显不是我们想要的,怎么搞?其实也不难,增加个方法去深度判断一级key还是二级key(这里将a和b理解为二级key,c为一级key),然后赋值即可。
function parse(url) {
  return url
    .slice(url.indexOf("?") + 1)
    .split("&")
    .reduce((prev, curr) => {
      let [key, val] = curr.split("=");
      if (!val) return prev;
        
      //  解析 'a[name]' -> [a, name]
      let path = key.split(/[\[\]]/g).filter(x => x);

      deep_parse(
        prev, // {}
        path, // [a, name]
        val // tiger
      );
     
      return prev;
    }, {});
}

function deep_parse(obj, path, val) {
  let i = 0;
  let deep_obj = {};
  // 处理二级key 只有二级key情况才会进入for循环 i会等于1
  for (; i < path.length - 1; i++) {
    if (!obj[path[i]]) {
      obj[path[i]] = {};
    }
    deep_obj = obj[path[i]];
  }
  
  if (i === 0) {
    // 一级key情况 c
    obj[path[i]] = val;
  } else {
    // 二级key情况 a b
    deep_obj[path[i]] = val;
  }
}

let result = parse(url);
console.log(result);
// { a: { name: 'tiger', age: '25' }, b: { name: 'cat' }, c: '666' }
此方法有两处可以替换或改进的地方。
1 . path解析
let path = key.split(/[\[\]]/g).filter(x => x)
可以替换成:
let path = key
    .replace("[", "]")
    .split("]")
    .filter(x => x)

一样的都是将'a[name]' 解析为 [a, name]

2 . deep_parse方法中的 deep_obj 有必要创建吗?
其实是没必要的,我们知道JS中的对象是引用类型新创建一个内存地址来引用其他对象直接用原对象obj的引用 没有区别,改变的仍是原对象obj。 所以 deep_parse方法也可以这样写:

function deep_parse(obj, path, val) {
  let i = 0;
  // 处理二级key 只有二级key情况才会进入for循环 i会等于1
  for (; i < path.length - 1; i++) {
    if (!obj[path[i]]) {
      obj[path[i]] = {};
    }
    obj = obj[path[i]];
  }
  
  // 一级key情况 c + 二级key情况 a b
  obj[path[i]] = val;
}
坑3 数组的querystring
如果参数长这样子怎么搞?
let url = "http://www.xxx.com?a[0]=1&a[1]=2&b[0]=3&c=4"
单独拎出来:
a[0]=1&a[1]=2&b[0]=3&c=4
其实就是解析成如下的一个对象,其中a和b是数组:
{ a: [ '1', '2' ], b: [ '3' ], c: '4' }
如果还用上方方法会解析成如下:
{ a: { '0': '1', '1': '2' }, b: { '0': '3' }, c: '4' }
貌似不太对啊,数组的下标变成了对象的key。思考一个问题: 对象有key,但是数组有key吗?数组没有key 但是数组有索引啊。那将数组替换成对象,再把对象的key变成数组的索引不就行了?
function parse(url) {
  return url
    .slice(url.indexOf("?") + 1)
    .split("&")
    .reduce((prev, curr) => {
      let [key, val] = curr.split("=");
      if (!val) return prev;

      //  解析 'a[name]' -> [a, name]
      let path = key.split(/[\[\]]/g).filter(x => x);

      deep_parse(
        prev, // {}
        path, // [a, name]
        val // tiger
      );

      return prev;
    }, {});
}

function deep_parse(obj, path, val) {
  let i = 0;
  // 处理二级key 只有二级key情况才会进入for循环 i会等于1
  for (; i < path.length - 1; i++) {
    if (!obj[path[i]]) {
      // 判断第二个 key 是否是数字
      if (path[i + 1].match(/^\d+$/)) {
        obj[path[i]] = [];
      } else {
        obj[path[i]] = {};
      }
    }
    obj = obj[path[i]];
  }

  obj[path[i]] = val;
}


let result = parse(url);
console.log(result);
// { a: [ '1', '2' ], b: [ '3' ], c: '4' }
可以看到只是稍稍改变了下deep_parse方法for循环中的if判断条件。如果第二个key是数字,那么第一个key的val就是数组类型即可。
坑4 空格要解码
如果参数长这样子怎么搞?
let url = "http://www.xxx.com?name=funky%20tiger"
也就是要对空格(%20)进行解码。比较简单,直接decodeURIComponent
// 和上方代码一样
// ...
function deep_parse(obj, path, val) {
  let i = 0;
  for (; i < path.length - 1; i++) {
    if (!obj[path[i]]) {
      if (path[i + 1].match(/^\d+$/)) {
        obj[path[i]] = [];
      } else {
        obj[path[i]] = {};
      }
    }
    obj = obj[path[i]];
  }
    
  // decodeURIComponent解码
  obj[path[i]] = decodeURIComponent(val);
}

let result = parse(url);
console.log(result);
// { name: 'funky tiger' }

Funky_Tiger
443 声望33 粉丝

刷题,交流,offer,内推,涨薪,相亲,前端资源共享...