zoukankan      html  css  js  c++  java
  • 打造属于前端的Uri解析器

    今天和大家一起讨论一下如何打造一个属于前端的url参数解析器。如果你是一个Web开发工程师,如果你了解过后端开发语言,譬如:PHP,Java等,那么你对下面的代码应该不会陌生:

    1 $kw = $_GET['keyword']; // PHP
    2 String kw = request.getParameter("keyword"); // JSP

    对于后端语言,通过上面的代码我们可以很方便的获取到一个url请求中的参数值。但是,当我们在一个Web前端工程中需要使用到url参数的时候,我们熟悉的JavaScript却没有提供类似方便的使用方法。那么,我们前端开发工程师该如何去获取url参数呢?方法挺多的,咱一个一个来看。

    使用字符串的split方法

    基本思路:首先我们通过参数连接符 & 将整个search串split成类似 key=value 的子串数组,然后遍历得到的数组元素,根据 = 运算符将每个子串拆分为 key 和 value ,最后将结果存储到一个json对象中,就得到我们的结果了。取值操作只需要从最终得到的json对象中取响应key对应的值就好了。原理很简单,我们来看下具体代码:

     1 function query(search) {
     2     var s = search || location.search,
     3         str = s && /^?/.test(s) ? s.slice(1) : s,
     4         r = {},
     5         kvs = str.split("&");
     6     for (var i = 0, len = kvs.length; i < len; i++) {
     7         var kv = kvs[i].split("=");
     8         r[kv[0]] = kv[1];
     9     }
    10     return r;
    11 }
    12 // use
    13 query("a=1&b=2&c=3"); // {"a":"1","b":"2","c":"3"}

    当然,如果想更直接一点可以写成下面这种:

     1 function query(search, key) {
     2     var s = search || location.search,
     3         str = s && /^?/.test(s) ? s.slice(1) : s,
     4         r = {},
     5         kvs = str.split("&");
     6     for (var i = 0, len = kvs.length; i < len; i++) {
     7         var kv = kvs[i].split("=");
     8         r[kv[0]] = kv[1];
     9     }
    10     return r[key];
    11 }
    12 // use
    13 query("a=1&b=2&c=3", "a"); // 1

    不过,显然第二种方式不好,每取一次值都得去跑一遍循环,太浪费资源。那么如果非得这么用,有没有什么更简洁一点的方式呢?答案是肯定的。

    使用字符串的match方法

    基本思路:使用match方法,从目标字符串中匹配与key对应的参数的值并返回。使用正则表达式匹配,可以省去循环,可以说是第二种split用法的升级版(仅从省代码考虑,性能恐怕未必),具体代码如下:

    1 function query(search, key) {
    2     var reg = new RegExp("(^|\?|\&)" + key + "=([^&$]*)", ""),
    3         match = null;
    4     match = search.match(reg);
    5     return match && match[2] ? match[2] : undefined;
    6 }
    7 // use
    8 query("a=1&b=2&c=3", "a"); // 1
    9 query("a=1&b&c=3", "b"); // undefined

    从代码实现来看,好像是比使用split来实现简单了很多,从测试结果看,二者效果完全一致,看来是没什么问题。接下来我们看一个和第一种split实现结果一致的另一种实现方法。

    使用正则表达式的exec方法

    基本思路:思路和split实现的思路大同小异,只是我们不在根据特殊符号进行字符串拆分,转而使用正则表达式对特征字符串进行匹配,再从匹配结果中获取我们需要的内容。代码如下:

     1 function query(search){
     2     var search = search || location.search,
     3         reg = /([?&])?([^=]+?)(?=(=|&|$))(([^&$]*))?/g,
     4         r = {},
     5         match = null;
     6     while(match = reg.exec(search)){
     7         r[match[2]] = match[4].replace(/^=/, "");
     8     }
     9     return r;
    10 }
    11 // use
    12 query("a=1&b=2&c=3"); // {a: "1", b: "2", c: "3"}
    13 query("a=1&b&c=2"); // {a: "1", b: undefined, c: "2"}

    到此,看起来一切都很顺利,也没出现什么问题。然而,事实真的如此吗?

    潜藏的那些坑

    首先,我们得考虑一个问题,大多真实情况下我们都是从浏览器地址栏直接拿search串来获取参数值,并不是像上面我们测试写的那样手动准备 a=1&b=2&c=3 ,而我们又知道浏览器自身有对中文和一些特殊符号进行encode的功能,那么问题来了,当出现这种情况的时候,我们将会得到什么呢?

    1 // 准备一个中文串(a=中国&b=China)
    2 // 将中文encode一下(a=%E4%B8%AD%E5%9B%BD&b=China)
    3 var p = query("a=%E4%B8%AD%E5%9B%BD&b=China"); // {a: "%E4%B8%AD%E5%9B%BD", b: "China"}
    4 console.log(p.a); // %E4%B8%AD%E5%9B%BD(看不懂啊!看不懂!)

    这样肯定不行,我们得想办法搞定它,以exec的方式为例,我们代码稍作调整:

     1 function query(search){
     2     var search = search || location.search,
     3         reg = /([?&])?([^=]+?)(?=(=|&|$))(([^&$]*))?/g,
     4         r = {},
     5         match = null;
     6     while(match = reg.exec(search)){
     7         r[match[2]] = decodeURIComponent(match[4]).replace(/^=/, "");
     8     }
     9     return r;
    10 }

    现在再来一遍:

    1 var p = query("a=%E4%B8%AD%E5%9B%BD&b=China"); // {a: "中国", b: "China"}

    这就对了,终于能看懂了!

    然后,我们再来看一种情况。以登录为例,通常我们登录成功后希望能跳转回到来源页面。为了达到这个目的,一般我们会为登录页面添加一个redirect的url参数,形如:

    1 http://www.xxx.com/login.jsp?a=1&b=2&redirect=http://www.yyy.cn/index.html

    我们先来试下,看看上面那个链接中我们的参数能不能正常解析:

    1 // 查询串为:a=1&b=2&redirect=http://www.yyy.cn/index.html
    2 query('a=1&b=2&redirect=http://www.yyy.cn/index.html');
    3 // {a: "1", b: "2", redirect: "http://www.yyy.cn/index.html"}

    OK,执行正常,没有问题,接下来我们稍微对我们的需求做点加工,我希望登录成功后调回 http://www.yyy.cn/index.html?sub=search&keyword=中国&lang=cn ,那么我们的查询串就应该是下面这个结果:

    1 a=1&b=2&redirect=http://www.yyy.cn/index.html?sub=search&keyword=中国&lang=cn

    按照顺理成章的逻辑,似乎没有问题吧?我们再来执行一下我们的query方法:

     1 query("a=1&b=2&redirect=http://www.yyy.cn/index.html?sub=search&keyword=中国&lang=cn");
     2 /* 
     3  * {
     4  *     a: "1",
     5  *     b: "2",
     6  *     redirect: "http://www.yyy.cn/index.html?sub=search",
     7  *     keyword: "中国",
     8  *     lang: "cn"
     9  * }
    10  */

    发现问题了吗?对!咱的跳转链接的被拆分成几个url参数了,显然咱达不到跳转回来源链接的目的了。那这个问题如何解决呢?从我们方法实现的角度去考虑,暂时还想不到解决办法,翻查了一下淘宝KISSY框架的Uri.Query类,简单的测试了下,结果和上面是一样的。倒是从使用咱方法的角度去着手,有一个解决方法——将redirect链接做一次encode,如下:

    1 query("a=1&b=2&redirect=" + encodeURIComponent("http://www.yyy.cn/index.html?sub=search&keyword=中国&lang=cn"));
    2 /* 
    3  * {
    4  *     a: "1",
    5  *     b: "2",
    6  *     redirect: "http://www.yyy.cn/index.html?sub=search&keyword=中国&lang=cn"
    7  * }
    8  */

    OK,结果正常了!由此,也可以映射一个问题,当我们在地址栏传递数据时,还是尽可能的encode好后再使用,这样可以避免一些不必要的麻烦。

    封装

    笔者喜欢把玩正则表达式(虽然还玩得不好),就以exec方式为例,对url参数解析的功能做了一下简单的封装,欢迎读者朋友批评指正:

     1 (function(window, undefined){
     2     var URI = {};
     3     URI.query = function(search){
     4         var s = search || location.search,
     5             reg = /([?&])?([^=]+?)(?=(=|&|$))(([^&$]*))?/g,
     6             r = {},
     7             match = null,
     8             total = 0;
     9         var _remove = function(key) {
    10             // r[key] = undefined;
    11             delete r[key];
    12             total--;
    13         };
    14         while(match = reg.exec(s)){
    15             var val = decodeURIComponent(match[4]).replace(/^=/, "");
    16             if (match[2].indexOf('[]') !== -1) {
    17                 var k = match[2].replace('[]', '');
    18                 if (typeof r[k] === 'undefined') {
    19                     r[k] = [val];
    20                     total++;
    21                 } else {
    22                     r[k].push(val);
    23                 }
    24             } else {
    25                 r[match[2]] = val;
    26                 total++;
    27             }
    28         }
    29         return {
    30             get: function(key) {
    31                 return r[key];
    32             },
    33             keys: function() {
    34                 var keys = [];
    35                 if ('keys' in Object) {
    36                     keys = Object.keys(r);
    37                 } else {
    38                     for (var key in r) {
    39                         keys.push(key);
    40                     }
    41                 }
    42                 return keys;
    43             },
    44             remove: _remove,
    45             count: function() {
    46                 return total;
    47             }
    48         };
    49     };
    50     window.Uri = window.Uri || URI;
    51 })(window);

    用法当然很简单:

    1 var q = Uri.query('a=person&b=人&c=people&d=中国人');
    2 q.keys(); // ["a", "b", "c", "d"]
    3 q.get('d'); // 中国人
    4 q.count(); // 4

    至此,我们今天讨论的话题就完成了。以上只是一个雏形,有兴趣的朋友可以进行扩展优化。欢迎大家发表各自的意见,多多交流,共同进步!

    作者博客:百码山庄

  • 相关阅读:
    反Secure Boot垄断:兼谈如何在Windows 8电脑上安装Linux
    火车售票系统(数据结构课设)
    货物管理系统(数据结构链式表)
    货物管理系统(数据结构顺序表)
    进制转换器(十进制转n进制)
    大学生成绩管理系统(C语言)
    如何对Linux的grub进行加密
    戴文的Linux内核专题:07内核配置(3)
    戴文的Linux内核专题:06配置内核(2)
    戴文的Linux内核专题:05配置内核(1)
  • 原文地址:https://www.cnblogs.com/mawuhen/p/4287729.html
Copyright © 2011-2022 走看看