前端路由
- 定义:在单页面应用,大部分页面结构不变,只改变部分内容的使用
- 优点:用户体验好,不需要每次都从服务器全部获取,快速展示给用户
- 缺点:使用浏览器的前进,后退键的时候会重新发送请求,没有合理地利用缓存。单页面无法记住之前滚动的位置,无法在前进,后退的时候记住滚动的位置。
后端路由
通过用户请求的url导航到具体的html页面;每跳转到不同的URL,都是重新访问服务端,然后服务端返回页面,页面也可以是服务端获取数据,然后和模板组合,返回HTML,也可以是直接返回模板HTML,然后由前端js再去请求数据,使用前端模板和数据进行组合,生成想要的HTML
前后端路由对比
从性能和用户体验的层面来比较的话,后端路由每次访问一个新页面的时候都要向服务器发送请求,然后服务器再响应请求,这个过程肯定会有延迟。而前端路由在访问一个新页面的时候仅仅是变换了一下路径而已,没有了网络延迟,对于用户体验来说会有相当大的提升。
在某些场合中,用ajax请求,可以让页面无刷新,页面变了但Url没有变化,用户就不能复制到想要的地址,用前端路由做单页面网页就很好的解决了这个问题。但是前端路由使用浏览器的前进,后退键的时候会重新发送请求,没有合理地利用缓存。
单页面的优势
- 不存在页面切换问题,因为只在同一个页面间切换,会更流畅,而且可以附加各种动画和过度效果,用户体验更好。
- 可以用到vue的路由和状态保持,不用担心切换造成的数据不同步。
- 打包方便,有现成的脚手架可以用,也比较不容易出问题
只有一张Web页面的应用,是一种从Web服务器加载的富客户端,单页面跳转仅刷新局部资源 ,公共资源(js、css等)仅需加载一次 页面跳转:使用js中的append/remove或者show/hide的方式来进行页面内容的更换; 数据传递:可通过全局变量或者参数传递,进行相关数据交互
使用场景: 适用于高度追求高度支持搜索引擎的应用
多页面的优势
- 逻辑清楚,各个页面按照功能和逻辑划分,不用担心业务复杂度
- 单个页面体积较小,加载速度比较有保证
- 多页面跳转需要刷新所有资源,每个公共资源(js、css等)需选择性重新加载
- 页面跳转:使用window.location.href = “./index.html”进行页面间的跳转;
- 数据传递:可以使用path?account=”123”&password=””路径携带数据传递的方式,或者localstorage、cookie等存储方式
使用场景: 高要求的体验度,追求界面流畅的应用
多页面的劣势
- 重复代码较多
- 页面经常需要切换,切换效果取决于浏览器和网络情况,对用户体验会有一定负面影响
- 无法充分利用vue的路由和状态保持,在多个页面之间共享和同步数据状态会成为一个难题
hash 模式
这里的 hash 就是指 url 后的 # 号以及后面的字符。比如说 “www.baidu.com/#hashhash" ,其中 “#hashhash” 就是我们期望的 hash 值。
由于 hash 值的变化不会导致浏览器像服务器发送请求,而且 hash 的改变会触发 hashchange 事件,浏览器的前进后退也能对其进行控制,所以在 H5 的 history 模式出现之前,基本都是使用 hash 模式来实现前端路由。
1 2 3 4 5
| window.addEventListener('hashchange', function(event){ let newURL = event.newURL; let oldURL = event.oldURL; },false)
|
下面实现一个路由对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
| class HashRouter{ constructor(){ this.routers = {}; window.addEventListener('hashchange',this.load.bind(this),false) } register(hash,callback = function(){}){ this.routers[hash] = callback; } registerIndex(callback = function(){}){ this.routers['index'] = callback; } registerNotFound(callback = function(){}){ this.routers['404'] = callback; } registerError(callback = function(){}){ this.routers['error'] = callback; } load(){ let hash = location.hash.slice(1), handler; if(!hash){ handler = this.routers.index; } else if(!this.routers.hasOwnProperty(hash)){ handler = this.routers['404'] || function(){}; } else{ handler = this.routers[hash] } try{ handler.apply(this); }catch(e){ console.error(e); (this.routers['error'] || function(){}).call(this,e); } } }
|
再来一个例子
1 2 3 4 5 6 7 8 9 10
| <body> <div id="nav"> <a href="#/page1">page1</a> <a href="#/page2">page2</a> <a href="#/page3">page3</a> <a href="#/page4">page4</a> <a href="#/page5">page5</a> </div> <div id="container"></div> </body>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| let router = new HashRouter(); let container = document.getElementById('container');
router.registerIndex(()=> container.innerHTML = '我是首页');
router.register('/page1',()=> container.innerHTML = '我是page1'); router.register('/page2',()=> container.innerHTML = '我是page2'); router.register('/page3',()=> container.innerHTML = '我是page3'); router.register('/page4',()=> {throw new Error('抛出一个异常')});
router.load();
router.registerNotFound(()=>container.innerHTML = '页面未找到');
router.registerError((e)=>container.innerHTML = '页面异常,错误消息:<br>' + e.message);
|
history 模式
在 HTML5 之前,浏览器就已经有了 history 对象。但在早期的 history 中只能用于多页面的跳转:
1 2 3 4
| history.go(-1); history.go(2); history.forward(); history.back();
|
在 HTML5 的规范中,history 新增了以下几个 API
1 2 3
| history.pushState(); history.replaceState(); history.state
|
由于 history.pushState() 和 history.replaceState() 可以改变 url 同时,不会刷新页面,所以在 HTML5 中的 histroy 具备了实现前端路由的能力。
对于单页应用的 history 模式而言,url 的改变只能由下面四种方式引起:
- 点击浏览器的前进或后退按钮
- 点击 a 标签
- 在 JS 代码中触发 history.pushState 函数
- 在 JS 代码中触发 history.replaceState 函数
下面实现一个路由对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75
| class HistoryRouter{ constructor(){ this.routers = {}; this.listenPopState(); this.listenLink(); } listenPopState(){ window.addEventListener('popstate',(e)=>{ let state = e.state || {}, path = state.path || ''; this.dealPathHandler(path) },false) } listenLink(){ window.addEventListener('click',(e)=>{ let dom = e.target; if(dom.tagName.toUpperCase() === 'A' && dom.getAttribute('href')){ e.preventDefault() this.assign(dom.getAttribute('href')); } },false) } load(){ let path = location.pathname; this.dealPathHandler(path) } register(path,callback = function(){}){ this.routers[path] = callback; } registerIndex(callback = function(){}){ this.routers['/'] = callback; } registerNotFound(callback = function(){}){ this.routers['404'] = callback; } registerError(callback = function(){}){ this.routers['error'] = callback; } assign(path){ history.pushState({path},null,path); this.dealPathHandler(path) } replace(path){ history.replaceState({path},null,path); this.dealPathHandler(path) } dealPathHandler(path){ let handler; if(!this.routers.hasOwnProperty(path)){ handler = this.routers['404'] || function(){}; } else{ handler = this.routers[path]; } try{ handler.call(this) }catch(e){ console.error(e); (this.routers['error'] || function(){}).call(this,e); } } }
|
再来一个例子
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| <body> <div id="nav"> <a href="/page1">page1</a> <a href="/page2">page2</a> <a href="/page3">page3</a> <a href="/page4">page4</a> <a href="/page5">page5</a> <button id="btn">page2</button> </div> <div id="container">
</div> </body>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| let router = new HistoryRouter(); let container = document.getElementById('container');
router.registerIndex(() => container.innerHTML = '我是首页');
router.register('/page1', () => container.innerHTML = '我是page1'); router.register('/page2', () => container.innerHTML = '我是page2'); router.register('/page3', () => container.innerHTML = '我是page3'); router.register('/page4', () => { throw new Error('抛出一个异常') });
document.getElementById('btn').onclick = () => router.assign('/page2')
router.registerNotFound(() => container.innerHTML = '页面未找到');
router.registerError((e) => container.innerHTML = '页面异常,错误消息:<br>' + e.message);
router.load();
|