在讲前端路由之前,先说下后端路由,以及为什么出现了前端路由。
后端路由: 浏览器在地址栏中切换不同的url时,每次都向后台服务器发出请求,服务器响应请求,在后台拼接html文件传给前端显示,java web中的jsp就是如此实现的。常用的后台MVC模式的基本路由处理流程:浏览器输入一个url请求,从中找到Controller和Action的值,将请求传递给Controller处理,Controller获取Model数据对象,并且将Model传递给View,最后View呈现界面。
例如输入一个url:localhost/home/index
其中localhost是域名,对应结构{controller}/{action}/{id}
缺点:当项目十分庞大时,加大了服务器端的压力,同时在浏览器端不能输入制定的url路径进行指定模块的访问。另外一个就是如果当前网速过慢,那将会延迟页面的加载,对用户体验不是很友好。
前端路由: 随着(SPA)单页应用的不断普及,前后端开发分离,目前项目基本都使用前端路由,在项目使用期间页面不会重新加载。
优点:1、用户体验好,和后台网速没有关系,不需要每次都从服务器全部获取,界面展现快。2、可以再浏览器中输入指定想要访问的url路径地址。3.实现了前后端的分离,方便开发。有很多框架都带有路由功能模块。
缺点:1、对SEO不是很友好2、在浏览器前进和后退时候重新发送请求,没有合理缓存数据。3,初始加载时候由于加载所有模块渲染,会慢一点。
前端路由目前主要有两种方法:
1、利用url的hash,就是常用的锚点(#)操作,类似页面中点击某小图标,返回页面顶部,JS通过hashChange事件来监听url的改变,IE7及以下需要轮询进行实现。一般常用框架的路由机制都是用的这种方法,例如Angualrjs自带的ngRoute和二次开发模块ui-router,react的react-route,vue-route… 2、利用HTML5的History模式,使url看起来类似普通网站,以”/”分割,没有”#”,但页面并没有跳转,不过使用这种模式需要服务器端的支持,服务器在接收到所有的请求后,都指向同一个html文件,通过historyAPI,监听popState事件,用pushState和replaceState来实现。具体API参考MDN文档https://developer.mozilla.org…由于使用hash方法能够兼容低版本的IE浏览器,简单的的自己搭建前端路由。
方法一:
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <title>前端路由实现</title> </head> <body> <ul> <li><a href="#/"></a></li> <li><a href="#/home">主页</a></li> <li><a href="#/about">详情页</a></li> </ul> <script type="text/javascript" src="router.js"></script> <script type="text/javascript"> var content = document.querySelector('body'); Router.route('/', function() { console.log("这是主页"); }); Router.route('/home', function() { console.log("这是主页"); }); Router.route('/about', function() { console.log("这是详情页"); }); </script> </body> </html>
router.js
//构造函数 function Router() { this.routes = {}; this.currentUrl = ''; } //route 存储路由更新时的回调到回调数组routes中,回调函数将负责对页面的更新 Router.prototype.route = function(path, callback) { this.routes[path] = callback || function(){};//给不同的hash设置不同的回调函数 }; //refresh 执行当前url对应的回调函数,更新页面 Router.prototype.refresh = function() { console.log(location.hash.slice(1));//获取到相应的hash值 this.currentUrl = location.hash.slice(1) || '/';//如果存在hash值则获取到,否则设置hash值为/ // console.log(this.currentUrl); this.routes[this.currentUrl]();//根据当前的hash值来调用相对应的回调函数 }; //init 监听浏览器url hash更新事件 Router.prototype.init = function() { window.addEventListener('load', this.refresh.bind(this), false); window.addEventListener('hashchange', this.refresh.bind(this), false); } //给window对象挂载属性 window.Router = new Router(); window.Router.init();
方法二:
<!DOCTYPE html> <html> <head> <meta charset="utf8" /> <script type="text/javascript" src="./router.js"></script> <style type="text/css"> </style> </head> <body> <li><a href="#/"></a></li> <li><a href="#/home">主页</a></li> <li><a href="#/about">详情页</a></li> </body> <script type="text/javascript"> window.onload = function() { var router = new Router(); router.add('home', function() { console.log('这是主页'); }); router.add('about', function() { console.log('这是详情页'); }); router.setIndex('home'); router.start(); }; </script> </html>
router.js
(function() { window.Router = function() { var self = this; self.hashList = {}; /* 路由表 */ self.index = null; self.key = '/'; window.onhashchange = function() { self.reload(); }; }; /** * 添加路由,如果路由已经存在则会覆盖 * @param addr: 地址 * @param callback: 回调函数,调用回调函数的时候同时也会传入相应参数 */ Router.prototype.add = function(addr, callback) { var self = this; self.hashList[addr] = callback; }; /** * 删除路由 * @param addr: 地址 */ Router.prototype.remove = function(addr) { var self = this; delete self.hashList[addr]; }; /** * 设置主页地址 * @param index: 主页地址 */ Router.prototype.setIndex = function(index) { var self = this; self.index = index; }; /** * 跳转到指定地址 * @param addr: 地址值 */ Router.prototype.go = function(addr) { var self = this; window.location.hash = '#' + self.key + addr; }; /** * 重载页面 */ Router.prototype.reload = function() { var self = this; var hash = window.location.hash.replace('#' + self.key, ''); var addr = hash.split('/')[0]; var cb = getCb(addr, self.hashList); if(cb != false) { var arr = hash.split('/'); arr.shift(); cb.apply(self, arr); } else { self.index && self.go(self.index); } }; /** * 开始路由,实际上只是为了当直接访问路由路由地址的时候能够及时调用回调 */ Router.prototype.start = function() { var self = this; self.reload(); } /** * 获取callback * @return false or callback */ function getCb(addr, hashList) { for(var key in hashList) { if(key == addr) { return hashList[key] } } return false; } })();
模拟hashchange 在低版本 IE 需要通过轮询监听 url 变化来实现:
(function(window) { // 如果浏览器不支持原生实现的事件,则开始模拟,否则退出。 if ( "onhashchange" in window.document.body ) { return; } var location = window.location, oldURL = location.href, oldHash = location.hash; // 每隔100ms检查hash是否发生变化 setInterval(function() { var newURL = location.href, newHash = location.hash; // hash发生变化且全局注册有onhashchange方法(这个名字是为了和模拟的事件名保持统一); if ( newHash != oldHash && typeof window.onhashchange === "function" ) { // 执行方法 window.onhashchange({ type: "hashchange", oldURL: oldURL, newURL: newURL }); oldURL = newURL; oldHash = newHash; } }, 100); })(window);
主要就是监听hash的变更,然后在里面根据配置,用ajax去读对应的界面片段,然后append到主容器中就可以了。市面上的选择比较多的是:director.js,path.js,Backbone.Router,leeluolee/stateman · GitHub