JS 如何取得 URL 里的参数

2019/03/14

上篇简单介绍了一下 Location 对象, 并封装了一个简单处理 URL 的方法, 本篇封装一个处理复杂 URL 的方法

要只是获取一些常规字符串倒没什么难的, 关键还有些乱七八糟的需求, 什么同一个参数名传递了多次啊, 传数组啊. 搞来搞去就写了一大堆

先说说思路吧, 如果你看这文章是想要解决问题, 拿着代码直接用的话, 就直接看最后面的 Code 实现以及使用方法吧

用框架思维分析问题

给你一个如下的 URL:

http://example.com:1234/test/t.asp?a=aa&b=bb&c&d=dd#Hello

将 URL 里传递的参数转换为 object 对象, 这样我们在使用参数的时候也更为方便

我曾多次强调框架思维, 现在遇到这个问题了, 我们就拿框架思维来分析一下, 该怎样才能快速解决

首先是要了解我们的目的是什么? 目的很简单, 取得 URL 内传递的参数, 并且解析成对象

接着再分析我们现在知道些什么? 有一串 URL

我们再来分析, 如果从 URL 中获得传递的参数, 也就是为了达到目的, 我们该做些什么?

URL 的特征我们大致都知道, 就是第一个 ? 后面的字符串, 都是传递的参数, 但是有个特殊情况请不要忘记了, URL 后面有时候会带上一个 # , 而 # 后面的内容, 并不是我们要传递的参数, 而是网页位置的标识符

如果 URL 中包含了 # , 我们只需要解析 ?# 之间的字符串就可以了, 如果不包含, 那么第一个 ? 后所有的内容都是我们需要解析的

好了, 分析完后, 我们按照上面的思路来逐步实现, 实现的时候可能会遇到其它的问题, 到时候再分析, 再解决

毕竟再牛逼的工程师, 也不会在动手前就想的面面俱到, 只能是在动手实现前尽可能的考虑周到, 遇到问题时再快速的迭代更新

JS 获取 URL 参数的过程

先用 JS 拿到 URL, 如果函数传参了 URL, 那就用参数. 如果没传参, 就使用当前页面的 URL

url = url || window.location.href
var queryString = url.split('?')[1]

如果后面的字符串存在 # , 我们还得将 # 后面的字符串去掉, 因为 # 后面的内容并不是我们需要获取的参数, 而是网页位置的标识符

queryString = queryString.split('#')[0]

好了, 把干扰的部分都移除后, 我们可以开始安心的解析参数了, 先将传递的参数分成数组

var arr = queryString.split('&')

现在我们可以获得一个字符串数组

['a=aa', 'b=bb', 'c', 'd=dd']

将字符串拆分成数组后, 我们通过创建一个对象, 用来存储我们所有的参数

var obj = {}

我们可以通过遍历数组 arr , 将它拆分成键值对. 把这个字符串做成 key:value 的对象

var a = arr[i].split('=')

接下来就是要为每一个变量 key 分配对应的值 value , 如果我们得到的 value 不是一个正确的参数, 我们就用 true 来表示这个参数名存在, 当然了, 你也可以根据自己的实际情况来做改变

var paramName = a[0]
var paramValue = typeof a[1] === 'undefined' ? true : a[1]

在这里我只是对 undefined 做了标记, 如果是 NaN , 我是直接拿它当字符串处理了

在这里有一个小坑得提醒一下, 我们在调用函数, 获取对象取值的时候, 如果 URL 传递的 key 为大写, 我们取对象时写的小写, 那么结果就是为 undefined

比如 URL 为 http://example.com:1234/test/t.asp?TeSt=TEst , 如果不做大小写的处理, 调用对象取值时 getAllUrlParams().TeSt 才能取到值 TEst , 如果做了处理, 我们使用时只需要全部写成小写/大写即可, 例如 getAllUrlParams().test

我在这就全部转为小写了, 如果你对大小写要求区分, 那到时候把这段 Code 给去掉就好了

paramName = paramName.toLowerCase()

接下来我们就要去处理我们接受到的 paramValue , 这些参数可能是索引数组, 非索引数组, 又或者是常规字符串

  • 如果是索引数组, 我们需要将 paramValue 转换成数组, 并且将索引对应的值, 放入索引对应的位置

  • 如果是非索引数组, 我们就要将 paramValue 放到数组中

  • 如果只是常规的字符串, 我们就需要为我们的对象 obj 创建一个常规的属性, 并为其分配值.

  • 如果这个 key 已经存在, 那么我们就要将现有的 paramValuekey:value 转换为数组, 并将它放到数组中

拿几个实际案例, 感受一下我们要做什么吧

// 索引数组
getAllUrlParams('http://example.com/?colors[0]=red&colors[2]=green&colors[6]=blue')
// { "colors": [ "red", null, "green", null, null, null, "blue" ] }

// 非索引数组
getAllUrlParams('http://example.com/?colors[]=red&colors[]=green&colors[]=blue')
// { "colors": [ "red", "green", "blue" ] }

// 多次传递同一个key
getAllUrlParams('http://example.com/?colors=red&colors=green&colors=blue')
// { "colors": [ "red", "green", "blue" ] }

// 传递了key,但是没传value
getAllUrlParams('http://example.com/?product=shirt&color=blue&newuser&size=m')
// { "product": "shirt", "color": "blue", "newuser": true, "size": "m" }

对应的代码实现如下:

// 如果 paramName 以方括号结束, e.g. colors[] or colors[2]
if (paramName.match(/\[(\d+)?\]$/)) {
  // 如果 paramName 不存在,则创建 key
  var key = paramName.replace(/\[(\d+)?\]/, '')
  if (!obj[key]) obj[key] = []
  // 如果是索引数组 e.g. colors[2]
  if (paramName.match(/\[\d+\]$/)) {
    // 获取索引值并在对应的位置添加值
    var index = /\[(\d+)\]/.exec(paramName)[1]
    obj[key][index] = paramValue
  } else {
    // 如果是其它的类型,也放到数组中
    obj[key].push(paramValue)
  }
} else {
  // 处理字符串类型
  if (!obj[paramName]) {
    // 如果如果 paramName 不存在,则创建对象的属性
    obj[paramName] = paramValue
  } else if (obj[paramName] && typeof obj[paramName] === 'string') {
    // 如果属性存在,并且是个字符串,那么就转换为数组
    obj[paramName] = [obj[paramName]]
    obj[paramName].push(paramValue)
  } else {
    // 如果是其它的类型,还是往数组里丢
    obj[paramName].push(paramValue)
  }
}

如果你的 URL 的传参包含了一些特殊字符, 比如空格. 例如 url="example.com/?name=na%20me" , 拿到对象值之后, 是需要解码后才能获得正确的值的

var original = getAllUrlParams().name // 'na%20me'
var decode = decodeURIComponent(original) // 'na me'

具体实现以及使用方式

下面是 JS 的具体的完整实现, 你们复制回去就可以用

function getAllUrlParams (url) {
  // 用 JS 拿到 URL,如果函数接收了 URL,那就用函数的参数。如果没传参,就使用当前页面的 URL
  url = url || window.location.href
  var queryString = url.split('?')[1]
  // 用来存储我们所有的参数
  var obj = {}
  // 如果没有传参,返回一个空对象
  if (!queryString) {
    return obj
  }
  // # 后的内容不是我们需要的
  queryString = queryString.split('#')[0]
  // 将参数分成数组
  var arr = queryString.split('&')
  for (var i = 0; i < arr.length; i++) {
    // 分离成 key:value 的形式
    var a = arr[i].split('=')
    // 将 undefined 标记为 true
    var paramName = a[0]
    var paramValue = typeof a[1] === 'undefined' ? true : a[1]
    // 如果调用对象时要求大小写区分,可删除这行代码
    paramName = paramName.toLowerCase()
    // 如果 paramName 以方括号结束, e.g. colors[] or colors[2]
    if (paramName.match(/\[(\d+)?\]$/)) {
      // 如果 paramName 不存在,则创建 key
      var key = paramName.replace(/\[(\d+)?\]/, '')
      if (!obj[key]) obj[key] = []
      // 如果是索引数组 e.g. colors[2]
      if (paramName.match(/\[\d+\]$/)) {
        // 获取索引值并在对应的位置添加值
        var index = /\[(\d+)\]/.exec(paramName)[1]
        obj[key][index] = paramValue
      } else {
        // 如果是其它的类型,也放到数组中
        obj[key].push(paramValue)
      }
    } else {
      // 处理字符串类型
      if (!obj[paramName]) {
        // 如果如果 paramName 不存在,则创建对象的属性
        obj[paramName] = paramValue
      } else if (obj[paramName] && typeof obj[paramName] === 'string') {
        // 如果属性存在,并且是个字符串,那么就转换为数组
        obj[paramName] = [obj[paramName]]
        obj[paramName].push(paramValue)
      } else {
        // 如果是其它的类型,还是往数组里丢
        obj[paramName].push(paramValue)
      }
    }
  }
  return obj
}

这个函数该怎么使用呢?

直接把 URL 参数当成对象调用就 OK 咯~

以文章开篇的 URL 为例子

var url = 'http://example.com:1234/test/t.asp?a=aa&b=bb&c&d=dd#Hello'

var urlParams = getAllUrlParams(url)

urlParams.a // 'aa'
urlParams.b // 'bb'
urlParams.c // true
urlParams.NB // undefined

你可以在下面输入框中输入需要查询的 url 来体验一下!

  

不兼容 IE 的解决方案

如果我们不需要考虑 IE 这种妖娆贱货, 以及一些非常老版本浏览器, 就用浏览器内 URLSearchParams 的接口吧. . . 这个接口可以直接拿取 URL 内的参数

var a = document.createElement('a')
a.href = 'http://example.com:1234/test/t.asp?a=aa&b=bb&c&d=dd#Hello'

var urlParams = new URLSearchParams(a.search) // 使用 search 时注意 # 和 ? 的先后位置

// 判断参数是否存在
console.log(urlParams.has('a')) // true
// 获取参数对应的值
console.log(urlParams.get('a')) // 'aa'

这个接口还提供了更多成熟的方法, 比如 keys() , Values() , 还有 entries() , 这个接口该怎么使用, 直接去看官方文档就好了, 用起来还是很虚浮的