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

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

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

使用字符串的split方法

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

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

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

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

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

使用字符串的match方法

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

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

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

使用正则表达式的exec方法

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

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

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

潜藏的那些坑

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

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

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

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

现在再来一遍:

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

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

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

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

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

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

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

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

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

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

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

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

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

封装

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

 (function(window, undefined){
var URI = {};
URI.query = function(search){
var s = search || location.search,
reg = /([?&])?([^=]+?)(?=(=|&|$))(([^&$]*))?/g,
r = {},
match = null,
total = 0;
var _remove = function(key) {
// r[key] = undefined;
delete r[key];
total--;
};
while(match = reg.exec(s)){
var val = decodeURIComponent(match[4]).replace(/^=/, "");
if (match[2].indexOf('[]') !== -1) {
var k = match[2].replace('[]', '');
if (typeof r[k] === 'undefined') {
r[k] = [val];
total++;
} else {
r[k].push(val);
}
} else {
r[match[2]] = val;
total++;
}
}
return {
get: function(key) {
return r[key];
},
keys: function() {
var keys = [];
if ('keys' in Object) {
keys = Object.keys(r);
} else {
for (var key in r) {
keys.push(key);
}
}
return keys;
},
remove: _remove,
count: function() {
return total;
}
};
};
window.Uri = window.Uri || URI;
})(window);

用法当然很简单:

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

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

作者博客:百码山庄

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

  1. XML技术之SAX解析器

    1.解析XML文件有三种解析方法:DOM SAX DOM4J. 2.首先SAX解析技术只能读取XML文档中的数据信息,不能对其文档中的数据进行添加,删除,修改操作:这就是SAX解析技术的一个缺陷. 3 ...

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

    SpringMVC简介:SpringMVC也叫Spring Web mvc,属于表现层的框架.Spring MVC是Spring框架的一部分,是在Spring3.0后发布的 Spring结构图 Spr ...

  3. atitit.java解析sql语言解析器解释器的实现

    atitit.java解析sql语言解析器解释器的实现 1. 解析sql的本质:实现一个4gl dsl编程语言的编译器 1 2. 解析sql的主要的流程,词法分析,而后进行语法分析,语义分析,构建sq ...

  4. android XML解析器全解案例

    1.使用pull解析 package com.example.myxml; import java.io.InputStream; import java.util.ArrayList; import ...

  5. dom4j解析器 基于dom4j的xpath技术 简单工厂设计模式 分层结构设计思想 SAX解析器 DOM编程

    *1 dom4j解析器   1)CRUD的含义:CreateReadUpdateDelete增删查改   2)XML解析器有二类,分别是DOM和SAX(simple Api for xml).     ...

  6. SAXParser 解析器和 XMLEventReader(读取XML文档)

    import javax.xml.parsers.ParserConfigurationException;import javax.xml.parsers.SAXParser;import java ...

  7. 一步一步自定义SpringMVC参数解析器

    随心所欲,自定义参数解析器绑定数据. 题图:from Zoommy 干货 SpringMVC解析器用于解析request请求参数并绑定数据到Controller的入参上. 自定义一个参数解析器需要实现 ...

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

    前言 为了保证每周一篇的进度,又由于Vitamio新版本没有发布, 决定推迟本地播放的一些功能(截图.视频时间.尺寸等),跳过直接写在线播放部分的章节.从Vitamio的介绍可以看得出,其支持http ...

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

    前言 本章继续完善播放相关播放器的核心功能,为后续扩展打好基础.   声明 欢迎转载,但请保留文章原始出处:)  博客园:http://www.cnblogs.com 农民伯伯: http://ove ...

随机推荐

  1. 5.JSON

    AJAX传递复杂数据如果自己进行格式定义的话会经历组装.解析的过程,因此AJAX中有一个事实上的数据传输标准JSON. JSON将复杂对象序列化为一个字符串,在浏览器端再将字符串反序列化为JavaSc ...

  2. Android程序安装后在模拟器上不显示,并且控制台显示The launch will only sync the application package on the device!

    初学安卓,今天写了一个小例子,可是eclipse控制台却提示 No Launcher activity found! The launch will only sync the application ...

  3. 【原】centos6.5下cdh4.6 Oozie安装

    0.oozie只需安装在一台服务器上,这里选择在namenode上来安装:安装用户为cloud-user 1.安装Oozie包:    sudo yum install -y oozie oozie- ...

  4. 2015/11/06 社保查询系统持续 挂机ing

  5. 【转】VS2013编译libjpeg库

    原文地址:http://blog.csdn.net/weixinhum/article/details/42718959 现在,很多图像处理工具和开源库都给出了图像解码的函数接口,然而有时这些接口并不 ...

  6. ios打包ipa的四种实用方法

    总结一下,目前.app包转为.ipa包的方法有以下几种: 1.Apple推荐的方式,即实用xcode的archive功能 Xcode菜单栏->Product->Archive->三选 ...

  7. C语言中数据类型转换的学习

    1. 整型和枚举类型数据的转换 测试代码如下: #include <stdio.h> typedef enum _E_TYPE_T {     E_TYPE_1 = -1,     E_T ...

  8. automake---让Makefile变得更专业一点儿

    一般我们装软件时,都要运行 ./configure --prefix=/usr/local make make install 看着不断刷新的屏幕,总感觉真得好高深呀,其实我们的程序也可以这样子. 下 ...

  9. codeforces 710C

    C. Magic Odd Square time limit per test 1 second memory limit per test 256 megabytes input standard ...

  10. Jquery案例——某网站品牌列表的效果

    一下是效果图.点击"显示全部品牌",高亮推荐品牌,并显示全部品牌. HTML文件: <!DOCTYPE html> <html lang="en&quo ...