100行代码实现现代版Router
原文:http://www.html-js.com/article/JavaScript-version-100-lines-of-code-to-achieve-a-modern-version-of-Router
当前到处可见单页应用,而对于单页应用来说我们必须有一个有效的路由机制。像Emberjs就是建立在一个Router类上的框架。虽然我不是太确信这个是不是我喜欢的东东,但是肯定的是AbsurdJS必须有一个内置的Router。和这个框架中的其他功能一样,这个Router应该非常小巧简单的。让我们看下这个模块应该是什么样子呢。编辑:github 原文链接:A modern JavaScript router in 100 lines
需求
这里设计的router应该是这样的:
- 少于100行代码
- 支持散列输入的URL,比如http://site.com#products/list
- 能够支持History API
- 提供简单可用的接口
- 不会自动运行
- 可以监听变化
- 采用单例模式
我决定只用一个router实例。这个可能是一个糟糕的选择,因为我曾经做过需要几个router的项目,但是反过来说着毕竟不常见。如果我们采用单例模式来实现我们将不用在对象和对象之间传递router,同时我们也不担心如何创建它。我们只需要一个实例,那我们就自然而然这样创建了:
var Router = {
routes: [],
mode: null,
root: '/'
}
这里有3个属性:
- routes-它用来保存当前已经注册的路由。
- mode-取值有hash和history两个选项,用来判断是否使用History API
- root-应用的根路径。只有当我们使用pushState我们才需要它。
配置
我们需要一个方法去启动router。虽然只要设定两个属性,但是我们最好还是使用一个方法来封装下。
var Router = {
routes: [],
mode: null,
root: '/',
config: function(options) {
this.mode = options && options.mode && options.mode == 'history'
&& !!(history.pushState) ? 'history' : 'hash';
this.root = options && options.root ? '/' + this.clearSlashes(options.root) + '/' : '/';
return this;
}
}
只有在支持pushState的情况下才会支持history模式,否则我们就运行于hash模式下。root默认被设定为‘/’。
获得当前URL
这是router中非常重要的一部分,因为它告诉我们当前我们在哪里。因为我们有两个模式,所以我们要一个if判断。
getFragment: function() {
var fragment = '';
if(this.mode === 'history') {
fragment = this.clearSlashes(decodeURI(location.pathname + location.search));
fragment = fragment.replace(/\?(.*)$/, '');
fragment = this.root != '/' ? fragment.replace(this.root, '') : fragment;
} else {
var match = window.location.href.match(/#(.*)$/);
fragment = match ? match[1] : '';
}
return this.clearSlashes(fragment);
}
两种条件下我们都是用了全局对象window.location。在history模式下我们需要删除掉URL中的root部分,同时还需要通过正则(/\?(.*)$/)去删除所有get的参数。hash模式下比较简单。注意下方法clearSlashes,它是用来删除斜线的。这非常有必要,因为我们不想强制开发者使用固定格式的URL。所有他传递进去后都转换为一个值。
clearSlashes: function(path) {
return path.toString().replace(/\/$/, '').replace(/^\//, '');
}
增加和删除route
设计AbsurdJS的时候,我是尽量把控制权交给开发者。在大多数router实现中,路由一般被设计成字符串,但是我倾向于正则表达式。这样通过传递疯狂的正则表达式,可以使系统的可扩展性更强。
add: function(re, handler) {
if(typeof re == 'function') {
handler = re;
re = '';
}
this.routes.push({ re: re, handler: handler});
return this;
}
这个方法用来填充routes数组。如果只传递了一个方法,那我们就把它当成一个默认路由处理器,并且把它当成一个空字符串。注意这里大多数方法返回了this,这是为了方便级联调用。
remove: function(param) {
for(var i=0, r; i<this.routes.length, r = this.routes[i]; i++) {
if(r.handler === param || r.re === param) {
this.routes.splice(i, 1);
return this;
}
}
return this;
}
如果我们传递一个合法的正则表达式或者handler给删除方法,那就可以执行删除了。
flush: function() {
this.routes = [];
this.mode = null;
this.root = '/';
return this;
}
有时候我们需要重置类,那我们就需要一个flush方法来执行重置。
Check-in
当前我们已经有增加和删除URL的API了,同时也要可以获得当前的地址。那么接下来的逻辑就是去比对注册了的实体。
check: function(f) {
var fragment = f || this.getFragment();
for(var i=0; i<this.routes.length; i++) {
var match = fragment.match(this.routes[i].re);
if(match) {
match.shift();
this.routes[i].handler.apply({}, match);
return this;
}
}
return this;
}
我们使用getFragment来创建fragment或者直接把函数的参数赋值给fragment。然后我们使用了一个循环来查找这个路由。如果没有匹配上,那match就为null,否则match的只应该是下面这样的[”products/12/edit/22”, “12”, “22”, index: 1, input: ”/products/12/edit/22”]。他是一个对象数组,包含了匹配上的字符串和子字符串。这意味着如果我们能够匹配第一个元素的话,我们就可以通过正则匹配动态的URL。例如:
Router
.add(/about/, function() {
console.log('about');
})
.add(/products\/(.*)\/edit\/(.*)/, function() {
console.log('products', arguments);
})
.add(function() {
console.log('default');
})
.check('/products/12/edit/22');
脚本输出:
products [”12”, “22”]
这就是我们能够处理动态URL的原因。
监控变化
我们不能一直运行check方法。我们需要一个在地址栏发生变化的时候通知我们的逻辑。我这里说的变化包括触发浏览器的返回按钮。如果你接触过History API的话你肯定会知道这里有个popstate 事件。它是当URL发生变化时候执行的一个回调。但是我发现一些浏览器在页面加载时候不会触发这个事件。这个浏览器处理不同让我不得不去寻找另一个解决方案。及时在mode被设定为hash的时候我也去执行监控,所以我决定使用setInterval。
listen: function() {
var self = this;
var current = self.getFragment();
var fn = function() {
if(current !== self.getFragment()) {
current = self.getFragment();
self.check(current);
}
}
clearInterval(this.interval);
this.interval = setInterval(fn, 50);
return this;
}
我需要保存一个最新的URL用于执行比较。
改变URL
在我们router的最后,我们需要一个方法可以改变当前的地址,同时也可以触发路由的回调。
navigate: function(path) {
path = path ? path : '';
if(this.mode === 'history') {
history.pushState(null, null, this.root + this.clearSlashes(path));
} else {
window.location.href.match(/#(.*)$/);
window.location.href = window.location.href.replace(/#(.*)$/, '') + '#' + path;
}
return this;
}
同样,我么能这对不同的模式做了分支判断。如果History API当前可用的话,我们就是用pushState,否则我们我们就是用window.location。
最终代码
下面是最终版本的router,并附了一个小例子:
- var Router = {
- routes: [],
- mode: null,
- root: '/',
- config: function(options) {
- this.mode = options && options.mode && options.mode == 'history'
- && !!(history.pushState) ? 'history' : 'hash';
- this.root = options && options.root ? '/' + this.clearSlashes(options.root) + '/' : '/';
- return this;
- },
- getFragment: function() {
- var fragment = '';
- if(this.mode === 'history') {
- fragment = this.clearSlashes(decodeURI(location.pathname + location.search));
- fragment = fragment.replace(/\?(.*)$/, '');
- fragment = this.root != '/' ? fragment.replace(this.root, '') : fragment;
- } else {
- var match = window.location.href.match(/#(.*)$/);
- fragment = match ? match[1] : '';
- }
- return this.clearSlashes(fragment);
- },
- clearSlashes: function(path) {
- return path.toString().replace(/\/$/, '').replace(/^\//, '');
- },
- add: function(re, handler) {
- if(typeof re == 'function') {
- handler = re;
- re = '';
- }
- this.routes.push({ re: re, handler: handler});
- return this;
- },
- remove: function(param) {
- for(var i=0, r; i<this.routes.length, r = this.routes[i]; i++) {
- if(r.handler === param || r.re === param) {
- this.routes.splice(i, 1);
- return this;
- }
- }
- return this;
- },
- flush: function() {
- this.routes = [];
- this.mode = null;
- this.root = '/';
- return this;
- },
- check: function(f) {
- var fragment = f || this.getFragment();
- for(var i=0; i<this.routes.length; i++) {
- var match = fragment.match(this.routes[i].re);
- if(match) {
- match.shift();
- this.routes[i].handler.apply({}, match);
- return this;
- }
- }
- return this;
- },
- listen: function() {
- var self = this;
- var current = self.getFragment();
- var fn = function() {
- if(current !== self.getFragment()) {
- current = self.getFragment();
- self.check(current);
- }
- }
- clearInterval(this.interval);
- this.interval = setInterval(fn, 50);
- return this;
- },
- navigate: function(path) {
- path = path ? path : '';
- if(this.mode === 'history') {
- history.pushState(null, null, this.root + this.clearSlashes(path));
- } else {
- window.location.href.match(/#(.*)$/);
- window.location.href = window.location.href.replace(/#(.*)$/, '') + '#' + path;
- }
- return this;
- }
- }
- // configuration
- Router.config({ mode: 'history'});
- // returning the user to the initial state
- Router.navigate();
- // adding routes
- Router
- .add(/about/, function() {
- console.log('about');
- })
- .add(/products\/(.*)\/edit\/(.*)/, function() {
- console.log('products', arguments);
- })
- .add(function() {
- console.log('default');
- })
- .check('/products/12/edit/22').listen();
- // forwarding
- Router.navigate('/about');
总结
router类大概有90行代码。它支持散列输入的RUL和History API。如果你不想用整个框架的话我想这个还是非常有用的。
这个类是AbsurdJS类的一部分,你可以在这里查看到这个类的说明。
源代码可以在github下载到。
100行代码实现现代版Router的更多相关文章
- 100行代码实现最简单的基于FFMPEG+SDL的视频播放器(SDL1.x)【转】
转自:http://blog.csdn.net/leixiaohua1020/article/details/8652605 版权声明:本文为博主原创文章,未经博主允许不得转载. 目录(?)[-] ...
- 【编程教室】PONG - 100行代码写一个弹球游戏
大家好,欢迎来到 Crossin的编程教室 ! 今天跟大家讲一讲:如何做游戏 游戏的主题是弹球游戏<PONG>,它是史上第一款街机游戏.因此选它作为我这个游戏开发系列的第一期主题. 游戏引 ...
- 第一行代码 Android 第二版到货啦
今日android第一行代码[第二版]已到,收获的季节到了 先看一下封面 书签: 以后就把空闲时间送给它吧 先来看一下本书的目录: 第1章 开始启程--你的第1行Android代码 第2章 先从看得到 ...
- 【转】100行代码实现最简单的基于FFMPEG+SDL的视频播放器
FFMPEG工程浩大,可以参考的书籍又不是很多,因此很多刚学习FFMPEG的人常常感觉到无从下手.我刚接触FFMPEG的时候也感觉不知从何学起. 因此我把自己做项目过程中实现的一个非常简单的视频播放器 ...
- 200行代码实现简版react🔥
200行代码实现简版react
- 用JavaCV改写“100行代码实现最简单的基于FFMPEG+SDL的视频播放器 ”
FFMPEG的文档少,JavaCV的文档就更少了.从网上找到这篇100行代码实现最简单的基于FFMPEG+SDL的视频播放器.地址是http://blog.csdn.net/leixiaohua102 ...
- 100行代码让您学会JavaScript原生的Proxy设计模式
面向对象设计里的设计模式之Proxy(代理)模式,相信很多朋友已经很熟悉了.比如我之前写过代理模式在Java中实现的两篇文章: Java代理设计模式(Proxy)的四种具体实现:静态代理和动态代理 J ...
- GuiLite 1.2 发布(希望通过这100+行代码来揭示:GuiLite的初始化,界面元素Layout,及消息映射的过程)
经过开发群的长期验证,我们发现:即使代码只有5千多行,也不意味着能够轻松弄懂代码意图.痛定思痛,我们发现:虽然每个函数都很简单(平均长度约为30行),可以逐个击破:但各个函数之间如何协作,却很难说明清 ...
- 100行代码实现HarmonyOS“画图”应用,eTS开发走起!
本期我们给大家带来的是"画图"应用开发者Rick的分享,希望能给你的HarmonyOS开发之旅带来启发~ 介绍 2021年的华为开发者大会(HDC2021)上,HarmonyOS ...
随机推荐
- Flask中的before_request和after_request
1.@app.before_request 在请求(request)之前做出响应 @app.before_request 也是一个装饰器,他所装饰的函数,都会在请求进入视图函数之前执行 2.@app. ...
- Codeforces 1058 D. Vasya and Triangle(分解因子)
题目:http://codeforces.com/contest/1058/problem/D 题意:有一个大小为N*M的矩阵内,构造一个三角形,使面积为(n*m)/k.若存在输出三个顶点(整数). ...
- 尝试利用slmail的漏洞来getshell
作者:Joe 本文属于Arctic shell原创内容计划文章,转载请注明原文地址! 二进制,计算机才可以理解的低级语言,简单来说它是一种信号,用电信号为例,0就是断电,而1就是有电,这样子010 ...
- Linux巩固记录(9) keepalived+nginx搭建高可用负载分发环境
环境准备(继续服用hadoop节点) slave1 192.168.2.201(CentOs 7) slave2 192.168.2.202(CentOs 7) slave1 和 slave2 上 ...
- 对drf的初步认识
web应用模式 1.前后端不分离 在前后端不分离的应用模式中,前端页面看到的效果都是由后端控制,由后端渲染页面或重定向,也就是后端需要控制前端的展示,前端与后端的耦合度很高. 这种应用模式比较适合纯网 ...
- vue中封装公共方法,全局使用
1.以封装的合并单元格为例,首先建立一个util.js 2.在main.js中引用 3.直接在使用该方法的地方调用就可以了
- 用开源 ASP.NET MVC 程序 Bonobo Git Server 搭建 Git 服务器(转)
用开源 ASP.NET MVC 程序 Bonobo Git Server 搭建 Git 服务器 现在不用Git,都不好意思说自己是程序员. 当你想用Git,而源代码服务器是Windows系统时,你 ...
- [工具]Tomcat CVE-2017-12615 远程代码执行
工具: K8_TocmatExp编译: VS2012 C# (.NET Framework v2.0)组织: K8搞基大队[K8team]作者: K8拉登哥哥博客: http://qqhack8.b ...
- 课程一(Neural Networks and Deep Learning),第二周(Basics of Neural Network programming)—— 2、编程作业常见问题与答案(Programming Assignment FAQ)
Please note that when you are working on the programming exercise you will find comments that say &q ...
- Nginx单向认证的安装配置
Nginx单向认证的安装配置 首先系统要已经安装了openssl,以下是使用openssl安装配置单向认证的执行步骤与脚本: #------------------------------------ ...