js解析一段URL地址栏参数格式化的代码

113 阅读2分钟

这是一到非常典型的面试题,潜意识里他没什么难的,但今天看到一位大神写出来的代码卓识有点水平,拿出来分析了一下,才有些领会为什么这道看似简单的面试题会经常的出现,包括一些大厂也经常问到,确实能提现资深的开发者和初级开发者的水准。

题:写一个程序,解析下面的queryString,返回对象

初级

function parse(str) {
  return str.split('&').reduce((o, kv) => {
    const [key, value] = kv.split("=");
    if(!value) {
      return o;
    }
    o[key] = value
    return o
  }, {})
}
let url = https://www.juejin.cn?name=js&age=20&phone=123456let 
let urlObj = parse(url)

如果在面试中三五分钟写出这段代码也还不错,能实现基本的要求,并且用reduce函数处理代码非常整洁,接着往下。

高级

上边的代码会有个坑,如果url是这样的:

https://juejin.cn?name=js&age=20&company[name]=xxx&company[address]=ooo

https://juejin.cn?a[name]=js&a[age]=20&a[company][name]=xxx&a[company][address]=ooo

代码:

function parse(str){
  return str.split('&').reduce((o, kv) => {
    const [key, value] = kv.split("=");
    if(!value) {
      return o;
    }
    deep_set(o, key.split(/[\[\]]/g).filter(x=>x), value)
    return o
  }, {})
}
function deep_set(o, path, value){
    let i = 0;
    for(; i < path.length -1; i++) {
        if(o[path[i]] === undefined) {
            o[path[i]] = {}    
        }    
        o = o[path[i]]  
    }
    o[path[i]] = value;
};
let url = https://www.juejin.cn?name=js&age=20&company[name]=xxx&company[address]=ooo;
let urlObj =parse(url)

这几行代码写出来以后就显得有点水平了,但是笔者第一次看 deep_set函数的时候有点懵逼,让我们来一起解析一下。

function deep_set(o, path, value) { //首先接受形参o
   let i = 0;  
   // path 是属性数组,循环次数length-1,最后项不循环    
   for(; i < path.length -1; i++) {
      if(o[path[i]] === undefined) {
          // 如果o[path[i]] 为 undefined 赋值 {}      
          o[path[i]] = {}
      }
      //又赋值回去给o(最不理解的是这行,赋值给o以后o不就变了嘛,原来的o不成了最新的了)
      o = o[path[i]] 
   }  
   o[path[i]] = value  //循环完以后再赋值
}

理解这段代码 要熟悉引用类型,还有函数赋值

// 知识点1:函数接收参数,本质上的执行赋值的过程
deep_set(o, key.split(/[[]]/g).filter(x=>x), value)
function deep_set(o, path, value) {
    // 实际上是执行了 o = o
}

// 知识点2:引用类型赋值,浅拷贝
描述太复杂举个例子:
var o = {a: 1, b: {c:2, d:3}, f: 5}
var a = o;    // a指向o
a = o['b'];   // a指向了o.b
a['e'] = {};  // 给a添加属性e,相当于 o.b 添加了一个属性e
a = a['e'];   // 又把a指向了e
a['g'] = 8;   // 给a添加一个属性g并且赋值8,相当于给对象e添加了一个属性g=8
console.log(a); // { g: 8 }
console.log(o); // { a: 1, b: { c: 2, d: 3, e: { g: 8 } }, f: 5 }

// 最终o的结果: { a: 1, b: { c: 2, d: 3, e: { g: 8 } }, f: 5 }

理解上述解析过程再回头看看代码是不是非常容易理解了,但是笔者认为原作者没有考虑到代码的可读性,所有我们换一下变量更能理解,如下:

function parse(str){
    return str.split('&').reduce((o, kv) => {
        const [key, value] = kv.split("=");
        if(!value) {
            return o;
        }    
        deep_set(o, key.split(/[[]]/g).filter(x=>x), value)    
        return o
    }, {})
}

function deep_set(o, path, value) {
  let a = o;
  let i = 0;
  for(; i < path.length -1; i++) {
      if(a[path[i]] === undefined) {
          a[path[i]] = {}    
      }
      a = a[path[i]]
  }  
  a[path[i]] = value
}
// 似乎这样的可读性高一些

还没完接着向下

大神

上边代码还有个坑,如果地址栏传出的是一个数组怎么办,例如下面 url里面的a

let url=juejin.cn?name=js&age=20&company[name]=xxx&company[address]=ooo&a[0]=1&a[1]=2;

// 代码
function parse(str){
    return str.split('&').reduce((o, kv) => {
        const [key, value] = kv.split("=");
        if(!value) {
            return o;
        }    
        deep_set(o, key.split(/[[]]/g).filter(x=>x), value)    
        return o
    }, {})
}

function deep_set(o, path, value) {
    let a = o;
    let i = 0;
    for(; i < path.length -1; i++) {
        if(a[path[i]] === undefined) {
            if(path[i+1].match(/^\d+$/)) { // 判断是否是数组
                a[path[i]] = []             
            } else {
                a[path[i]] = {}
            }    
        }    
        a = a[path[i]]  
     }  
     a[path[i]] = value
}

let urlObj =parse(url)

结束