打造属于前端的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         while(match = reg.exec(s)){
10             r[match[2]] = decodeURIComponent(match[4]).replace(/^=/, "");
11             total++;
12         }
13         return {
14             get: function(key) {
15                 return r[key];
16             },
17             keys: function() {
18                 var keys = [];
19                 if (‘keys‘ in Object) {
20                     keys = Object.keys(r);
21                 } else {
22                     for (var key in r) {
23                         keys.push(key);
24                     }
25                 }
26                 return keys;
27             },
28             count: function() {
29                 return total;
30             }
31         };
32     };
33     window.Uri = window.Uri || URI;
34 })(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

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

作者博客:百码山庄

时间: 2024-08-25 18:14:05

打造属于前端的Uri解析器的相关文章

【原创】打造属于前端的Uri解析器

今天和大家一起讨论一下如何打造一个属于前端的url参数解析器.如果你是一个Web开发工程师,如果你了解过后端开发语言,譬如:PHP,Java等,那么你对下面的代码应该不会陌生: $kw = $_GET['keyword']; // PHP String kw = request.getParameter("keyword"); // JSP 对于后端语言,通过上面的代码我们可以很方便的获取到一个url请求中的参数值.但是,当我们在一个Web前端工程中需要使用到url参数的时候,我们熟悉

springmvc 前端控制器,映射器,适配器,视图解析器

1.前端控制器DispatcherServlet的配置,在web.xml进行配置即可跟servlet的配置方式相同 1)contextConfigLocation配置sprimgmvc加载的配置文件(配置处理器映射器,适配器等等)如果不配置contextConfigLocation,默认加载的是/WEB-INF/servlet名称-servlet.xml(springmvc-servlet.xml) 2) 第一种:*.action,访问以.action结尾 由DispatcherServlet进

笔记:XML-解析文档-流机制解析器(SAX、StAX)

DOM 解析器完整的读入XML文档,然后将其转换成一个树型的数据结构,对于大多数应用,DOM 都运行很好,但是,如果文档很大,并且处理算法又非常简单,可以在运行时解析节点,而不必看到完整的树形结构,那么我们应该使用流机制解析器(streaming parser),Java 类库提供的流解析机制有 SAX 解析器和 StAX 解析器,SAX 解析器是基于事件回调机制,而 StAX解析器提供了解析事件的迭代器. 使用SAX解析器 SAX 解析器在解析XML 输入的组成部分时会报告事件,在使用 SAX

Android解析XML之SAX解析器

SAX(Simple API for XML)解析器是一种基于事件的解析器,它的核心是事件处理模式,主要是围绕着事件源以及事件处理器来工作的.当事件源产生事件后,调用事件处理器相应的处理方法,一个事件就可以得到处理.在事件源调用事件处理器中特定方法的时候,还要传递给事件处理器相应事件的状态信息,这样事件处理器才能够根据提供的事件信息来决定自己的行为. SAX解析器的优点是解析速度快,占用内存少.非常适合在Android移动设备中使用. SAX相关类及API DefaultHandler:是一个事

使用Vitamio打造自己的Android万能播放器(2)—— 手势控制亮度、音量、缩放

前言 本章继续完善播放相关播放器的核心功能,为后续扩展打好基础. 声明 欢迎转载,但请保留文章原始出处:) 博客园:http://www.cnblogs.com 农民伯伯: http://over140.cnblogs.com 系列 1.使用Vitamio打造自己的Android万能播放器(1)——准备 正文 一.实现目标 1.1 亮度控制 模仿VPlayer界面: 1.2 声音控制 模仿VPlayer界面: 1.3 画面缩放 根据下面API提供画面的拉伸.剪切.100%.全屏 二.Vitami

Spring MVC-控制器(Controller)-参数方法名称解析器(Parameter Method Name Resolver )示例(转载实践)

以下内容翻译自:https://www.tutorialspoint.com/springmvc/springmvc_parametermethodnameresolver.htm 说明:示例基于Spring MVC 4.1.6. 以下示例显示如何使用Spring Web MVC框架使用多操作控制器的参数方法名称解析器方法.MultiActionController类有助于分别在单个控制器中将多个URL与其方法映射. package com.tutorialspoint; import java

解析XML文件之使用SAM解析器

XML是一种常见的传输数据方式,所以在开发中,我们会遇到对XML文件进行解析的时候,本篇主要介绍使用SAM解析器,对XML文件进行解析. SAX解析器的长处是显而易见的,那就是SAX并不须要将全部的文档都载入内存之后才进行解析.SAX是事件驱动机制的,也就是碰到元素节点.文本节点.文档节点的时候,都会触发一定的事件.我们仅仅须要在对应的回调事件里面进行对应的处理就能够了.由于这个特点,所以SAX解析占用的内存比較少.其它的解析方式,比方下一节要介绍的DOM解析器,则占用内存比較多.在解析比較小的

SpringMVC入门案例及请求流程图(关于处理器或视图解析器或处理器映射器等的初步配置)

SpringMVC简介:SpringMVC也叫Spring Web mvc,属于表现层的框架.Spring MVC是Spring框架的一部分,是在Spring3.0后发布的 Spring结构图 SpringMVC请求流程图 SpringMVC请求流程图语述: request-------->DispatcherServler(中央调度器/前端控制器)-----> HandlerMapping(处理器映射器)------>返回一个执行链----->将执行链转交给HandlerAdap

使用Vitamio打造自己的Android万能播放器(5)——在线播放(播放优酷视频)

前言 为了保证每周一篇的进度,又由于Vitamio新版本没有发布, 决定推迟本地播放的一些功能(截图.视频时间.尺寸等),跳过直接写在线播放部分的章节.从Vitamio的介绍可以看得出,其支持http.m3u8等多种网络协议,本章将编写播放优酷视频的例子. 声明 欢迎转载,但请保留文章原始出处:) 博客园:http://www.cnblogs.com 农民伯伯: http://over140.cnblogs.com 系列 1.使用Vitamio打造自己的Android万能播放器(1)——准备 2