有一段时间没写过技术干货文了,这两天刚好遇到一个以前没太在意的一个功能实现-- 前端获取URL传递的参数

毕竟平时都是在后台处理,掉了一堆头发后,想想还是写出来跟你们分享一下,以后要是你们遇到了也有个参考

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

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

用框架思维分析问题

给你一个如下的URL:

NaoNao.com/?product=sh…

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

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

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

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

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

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

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

你可能觉得我是在说废话,这么明显的事情,只要不是白痴都能看得懂

我当然知道,只要不是白痴都能看得懂, 但我为什么要强调呢?因为我们想要快速的解决问题,必须具备框架思维,也可以说是工程思维

你可能有会说,这么简单的问题需要这样分析么?我们一看就知道了,闹闹你这是杀鸡用牛刀

虽说是杀鸡用牛刀,可 要想培养自己的工程思维,那么必须保持刻意训练,直到随手拈来

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

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

JS获取URL参数的过程

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

var queryString = url ? url.split('?')[1] : window.location.search.slice(1);
复制代码

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

queryString = queryString.split('#')[0];
复制代码

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

var arr = queryString.split('&');
复制代码

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

['product=shirt', 'color=blue', 'newuser', 'size=m']
复制代码

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

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://NaoNao.com/?NamE=NaoNao ,如果不做大小写的处理,调用对象取值时 getAllUrlParams().NamE 才能取到值 NaoNao ,如果做了处理,我们使用时只需要全部写成小写/大写即可,例如 getAllUrlParams().name

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

paramName = paramName.toLowerCase();
if (typeof paramValue === 'string') paramValue = paramValue.toLowerCase();
复制代码

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

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

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

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

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

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

// 索引数组
getAllUrlParams('http://NaoNao.com/?colors[0]=red&colors[2]=green&colors[6]=blue');
// { "colors": [ "red", null, "green", null, null, null, "blue" ] }
// 非索引数组
getAllUrlParams('http://NaoNao.com/?colors[]=red&colors[]=green&colors[]=blue');
// { "colors": [ "red", "green", "blue" ] }
// 多次传递同一个key
getAllUrlParams('http://NaoNao.com/?colors=red&colors=green&colors=blue');
// { "colors": [ "red", "green", "blue" ] }
// 传递了key,但是没传value
getAllUrlParams('http://NaoNao.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="NaoNao.com/?name=Nao%20Nao" ,拿到对象值之后,是需要解码后才能获得正确的值的

var original = getAllUrlParams().name; // 'Nao%20Nao'
var decode = decodeURIComponent(original); // 'Nao Nao'
复制代码

具体实现以及使用方式

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

function getAllUrlParams(url) {
    // 用JS拿到URL,如果函数接收了URL,那就用函数的参数。如果没传参,就使用当前页面的URL
    var queryString = url ? url.split('?')[1] : window.location.search.slice(1);
    // 用来存储我们所有的参数
    var obj = {};
    // 如果没有传参,返回一个空对象
    if (!queryString) {
        return obj;
    // stuff after # is not part of query string, so get rid of it
    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();
        if (typeof paramValue === 'string') paramValue = paramValue.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为例子

// http://NaoNao.com/?product=shirt&color=blue&newuser&size=m#Hello
getAllUrlParams().product; // 'shirt'
getAllUrlParams().color; // 'blue'
getAllUrlParams().newuser; // true
getAllUrlParams().NB; // undefined
getAllUrlParams('http://NaoNao.com/?NaoNao=shuai').NaoNao; // shuai
复制代码

不兼容IE的解决方案

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

// URL is http://NaoNao.com/?product=shirt&color=blue&newuser&size=m
const urlParams = new URLSearchParams(window.location.search);
// 判断参数是否存在
console.log(urlParams.has('product')); // true
// 获取参数对应的值
console.log(urlParams.get('product')); // "shirt"
复制代码

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