前端路由
什么是路由
对于没有后端项目经验或者SPA项目来说,路由可能会比较陌生。这里的路由不是指硬件路由,也不是我们通常所说的网络协议中的路由,但是它们表达的思想都是一样的,就是路径和资源的识别。
我们先来看访问一个网站,假如我们访问这三个页面:
http://sports.sina.com.cn/nba/
http://sports.sina.com.cn/tennis/
http://sports.sina.com.cn/csl/
那么其路径就分别是 /nba,/tennis,/csl
当用户使用http://sports.sina.com.cn/nba/来访问该页面时,Web 服务会接收到这个请求,然后会解析 URI 中的路径 /nba,在 Web 服务的程序中,该路径对应着相应的处理逻辑,程序会把请求交给路径所对应的处理逻辑,这样就完成了一次「路由分发」,这个分发就是通过「路由」来完成的。
前端路由
随着前端技术的发展,现在很多web应用都采用了SPA的形式,之前是通过服务端根据 url 的不同返回不同的页面实现无法满足需求,所以这里需要把不同路由对应不同的内容或页面的任务交给前端来做。通过这种前端路由实现的单页面应用有几个好处:
1、良好的前后端分离。SPA和RESTful架构一起使用,后端不再负责模板渲染、输出页面工作,web前端和各种移动终端地位对等,后端API通用化。
2、用户体验好、快,内容的改变不需要重新加载整个页面,web应用更具响应性和更令人着迷
3、同一套后端程序代码,不用修改就可以用于Web界面、手机、平板等多种客户端
但是同样也有其缺点
1、不利于SEO。
2、初次加载耗时相对增多。
3、导航不可用,如果一定要导航需要自行实现前进、后退。
接下来我们来看一下前端的路由是如何实现的。
原生路由的实现
在HTML5的 history API 出现之前,前端的路由都是通过 hash 来实现的,hash 能兼容低版本的浏览器。当 url 的 hash 发生变化时,触发 hashchange 注册的回调,回调中去进行不同的操作,进行不同的内容的展示。
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>路由</title> </head> <body> <ul> <li><a href="#/">默认路由</a></li> <li><a href="#/router1">路由1</a></li> <li><a href="#/router2">路由2</a></li> </ul> <div id="content"></div> </body> <script type="text/javascript"> class Router{ constructor(routes = {}) { this.routes = routes; this.curUrl = ""; } route(path,callback){ this.routes[path] = callback || function(){ } } refresh(){ this.curUrl = location.hash.slice(1) || '/'; if(this.routes[this.curUrl]){ this.routes[this.curUrl](); }else{ console.log('路由没有注册'); } } init(){ window.addEventListener('load', this.refresh.bind(this), false); window.addEventListener('hashchange', this.refresh.bind(this), false); } } function changeContent(text){ document.getElementById('content').innerHTML = text; } let router = new Router({ '/' : function(){ changeContent('默认路由'); }, '/router1':function(){ changeContent('路由1'); } }) router.route('/router2',function(){ changeContent('路由2'); }) router.init(); </script> </html>
HTML5 history API路由实现
html5 增加了两个方法,分别是pushState,replaceState。
pushState和replaceState是用来手动插入历史记录,然后执行AJAX请求,而popstate就是当浏览器前进后退的时候获得相应的state再执行相应操作
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>History Roter</title> </head> <body> <ul> <li><a href="#/">默认路由</a></li> <li><a href="#/router1">路由1</a></li> <li><a type="replace" href="#/router2">路由2</a></li> </ul> <div id="content"></div> <script> class Router { constructor(routes = {}) { this.routes = routes; this.curUrl = ""; } notify(state){ this.curUrl = state.path || '/'; if(this.routes[this.curUrl]){ this.routes[this.curUrl](); }else{ console.log('路由没有注册'); } } route(path,callback){ this.routes[path] = callback || function(){ } } init(){ let that = this; //浏览器点击前进后退时触发的事件 window.addEventListener('popstate',function(event){ that.notify(event.state || {}) }, false); //监控页面A标签,跳转做处理 document.querySelector('body').addEventListener('click', function(event){ if(event.target.tagName === 'A'){ let link = event.target.getAttribute('href'); if(!/^http/.test(link)){ event.preventDefault(); let path = link.slice(1) || '/'; that.notify({ 'path' : path} || {}) if(event.target.getAttribute('type') == 'replace'){ history.replaceState({ 'path' : path},'',event.target.href); }else{ history.pushState({ 'path' : path},'',event.target.href); } } } }, false) //首次进入页面进行路由 let path = location.hash.slice(1) || '/'; that.notify({ 'path' : path} || {}) } } function changeContent(text){ document.getElementById('content').innerHTML = text; } let router = new Router({ '/' : function(){ changeContent('默认路由'); }, '/router1':function(){ changeContent('路由1'); } }) router.route('/router2',function(){ changeContent('路由2'); }) router.init(); </script> </body> </html>