大部分web应用都需要解析URL,无论是提取域名、实现REST API,还是查找图片路径。一个典型的URL路径如下图所示:
你可以使用正则表达式把URL字符串拆分为连续的部分,但是这有点复杂而且没必要…
服务端URL解析
Node.js(及其分支,比如io.js)提供了URL API:
1 2 3 4 5 6 7 8 9 10 11 12 |
// Server-side JavaScript var urlapi = require('url'), url = urlapi.parse('http://site.com:81/path/page?a=1&b=2#hash'); console.log( url.href + 'n' + // the full URL url.protocol + 'n' + // http: url.hostname + 'n' + // site.com url.port + 'n' + // 81 url.pathname + 'n' + // /path/page url.search + 'n' + // ?a=1&b=2 url.hash // #hash ); |
你可以从上面的片段中看出,parse()方法返回一个包含所需数据(比如协议、主机名、端口等)的对象。
客户端URL解析
浏览器端没有对应的API。但是如果浏览器有件事做得好的话,那就是URL解析和DOM中的链接实现了类似的Location接口,例如:
1 2 3 4 5 6 7 8 9 10 11 12 |
// Client-side JavaScript // find the first link in the DOM var url = document.getElementsByTagName('a')[0]; console.log( url.href + 'n' + // the full URL url.protocol + 'n' + // http: url.hostname + 'n' + // site.com url.port + 'n' + // 81 url.pathname + 'n' + // /path/page url.search + 'n' + // ?a=1&b=2 url.hash // #hash ); |
如果我们把URL字符串放到内存的锚点元素(a)中,就可以不依赖正则表达式来解析,例如:
1 2 3 4 5 |
// Client-side JavaScript // create dummy link var url = document.createElement('a'); url.href = 'http://site.com:81/path/page?a=1&b=2#hash'; console.log(url.hostname); // site.com |
同构URL解析
Aurelio最近讨论了同构JavaScript应用。实质上,它是将渐进增强(progressive enhancement)推到了极致:应用可以在客户端或服务器上快乐地运行了。使用现代浏览器的用户可以使用单页应用。老式浏览器和搜索引擎爬虫将会看到服务端渲染的应用。理论上,应用可以根据设备的速度和带宽能力来实现不同等级的客户端/服务器处理。
同构JavaScript(Isomorphic JavaScript)已经被讨论过很多年,但是太复杂。很少项目能够在实现共享视图基础之上更进一步,而且标准渐进增强不起作用的情况也不多(如果没有更好地考虑到,大部分没有客户端JavaScript的“同构”框架都会失效)。意思是,可以实现环境无关的宏观库来试探性地迈出同构概念的第一步。
我们考虑下怎样在lib.js文件中编写一个URL解析库。首先,我们检测代码运行的位置:
1 2 |
// running on Node.js? var isNode = ( typeof module === 'object' && module.exports); |
这个方法不是特别健壮,因为你可能在客户端定义了module.exports函数,但是我不知道其他更好的方式(欢迎提供建议)。其他开发者的类似方法是检测window对象是否存在:
1 2 |
// running on Node.js? var isNode = typeof window === 'undefined'; |
现在我们使用URLparse函数完成lib.js代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
// lib.js library functions // running on Node.js? var isNode = ( typeof module === 'object' && module.exports); ( function (lib) { "use strict" ; // require Node URL API var url = (isNode ? require('url') : null); // parse URL lib.URLparse = function(str) { if (isNode) { return url.parse(str); } else { url = document.createElement('a'); url.href = str; return url; } } })(isNode ? module.exports : this.lib = {}); |
我在代码中使用isNode变量作澄清。但是,你可以直接把检测代码放到代码片段的最后一个圆括号内。
服务器端,URLparse导出为Common.JS模块。这样使用:
1 2 3 4 5 6 7 8 9 10 11 12 |
// include lib.js module var lib = require('./lib.js'); var url = lib.URLparse('http://site.com:81/path/page?a=1&b=2#hash'); console.log( url.href + 'n' + // the full URL url.protocol + 'n' + // http: url.hostname + 'n' + // site.com url.port + 'n' + // 81 url.pathname + 'n' + // /path/page url.search + 'n' + // ?a=1&b=2 url.hash // #hash ); |
客户端,URLparse作为全局lib对象的一个方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
<script src="./lib.js"></script> <script> var url = lib.URLparse('http://site.com:81/path/page?a=1&b=2#hash'); console.log( url.href + '\n' + // the full URL url.protocol + '\n' + // http: url.hostname + '\n' + // site.com url.port + '\n' + // 81 url.pathname + '\n' + // /path/page url.search + '\n' + // ?a=1&b=2 url.hash // #hash ); </script> |
除了库中包含的方法,客户端和服务端API都是相同的。
要承认的是,这是一个简单例子,URLparse在不同客户端和服务端上运行(大部分)不同代码。但是我们实现了一致性的API,可以说明JavaScript代码可以运行在任意位置。我们可以扩展这个库来提供更多客户端/服务器实用函数,比如字段验证、cookie解析、日期处理、货币格式等。
考虑到客户端和服务器上不同类型的逻辑,我不确定完全的同构应用是否具有实用性或者可行。但是,环境无关的库可以消除对相同功能编写两份代码的痛苦。