前端路由相关实现
来源:互联网 发布:波士顿矩阵作业 编辑:程序博客网 时间:2024/04/28 16:52
前端路由:客户端浏览器可以不依赖服务端,根据不同的URL渲染不同的视图页面。
前端路由实现思路
- 在页面不刷新的前提下实现url变化
- 捕捉到url的变化,以便执行页面替换逻辑
前端路由实现方法
HASH
我们经常在 url 中看到 #,这个 # 有两种情况,一个是我们所谓的锚点,比如典型的回到顶部按钮原理、Github 上各个标题之间的跳转等,路由里的 # 不叫锚点,我们称之为 hash,大型框架的路由系统大多都是哈希实现的。
同样我们需要一个根据监听哈希变化触发的事件 —— hashchange 事件
我们用 window.location 处理哈希的改变时不会重新渲染页面,而是当作新页面加到历史记录(session history)中,这样我们跳转页面就可以在 hashchange 事件中注册 ajax 从而改变页面内容。
利用hash值前端路由的简单实现
//index.html<ul> <li><a href='#blue'>blue</a></li> <li><a href='#yellow'>yellow</a></li> <li><a href='#red'>red</a></li></ul>
//jsfunction Router() { this.routes = {}; //初始化load一次 window.addEventListener('load', this.refresh.bind(this), false); window.addEventListener('hashchange', this.refresh.bind(this), false); } Router.prototype.route = function (path, callback) { this.routes[path] = callback || function () {}; }; Router.prototype.refresh = function () { this.currentHash = location.hash.slice(1) || '/'; typeof this.routes[this.currentHash] === 'function' && this.routes[this.currentHash](); }; var router = new Router(); router.route('blue', function () { document.body.style.backgroundColor = 'blue'; }); router.route('yellow', function () { document.body.style.backgroundColor = 'yellow'; }); router.route('red', function () { document.body.style.backgroundColor = 'red'; })
对于低版本的浏览器,例如IE6,7等等,不支持 hashchange 事件。这个时候我们只能通过 setInterval 设置心跳的方式去模拟 hashchange。
(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);
history API
history 的方法可以查看这篇文章:http://blog.csdn.net/wkyseo/article/details/51699770
重点说其中的两个新增的API history.pushState 和 history.replaceState
这两个 API 都接收三个参数,分别是
- 状态对象(state object) — 一个JavaScript对象,与用pushState()方法创建的新历史记录条目关联,pushState不会触发popstate事件。
- 标题(title) — FireFox浏览器目前会忽略该参数,虽然以后可能会用上。考虑到未来可能会对该方法进行修改,传一个空字符串会比较安全。或者,你也可以传入一个简短的标题,标明将要进入的状态。
- 地址(URL) — 新的历史记录条目的地址。浏览器不会在调用pushState()方法后加载该地址,但之后,可能会试图加载,例如用户重启浏览器。新的URL不一定是绝对路径;如果是相对路径,它将以当前URL为基准;传入的URL与当前URL应该是同源的,否则,pushState()会抛出异常。该参数是可选的;不指定的话则为文档当前URL。
相同之处是两个 API 都会操作浏览器的历史记录,而不会引起页面的刷新。
不同之处浏览器针对每个页面维护一个History栈。执行pushState函数可压入设定的url至栈顶,同时修改当前指针;当执行back操作时,history栈大小并不会改变(history.length不变),仅仅移动当前指针的位置;若当前指针在history栈的中间位置(非栈顶),此时执行pushState会改变history栈的大小。总结pushState的规律,可发现当前指针在history栈顶部时执行pushState,会增加history栈大小;若current指针不在栈顶则会在当前指针所在位置添加项。执行back操作并不修改history栈大小,因此可以通过back和forward在当前大小的history栈中自由移动。replaceState则仅会替换当前的历史记录。
改变history栈的current指针都会触发popstate事件,但是pushstate不会触发popstate事件,虽然current指针会在栈顶,并且改变history的length大小,但是切记pushstate不会触发popstate事件,之前写代码的时候逻辑混乱,导致触发不了。
写了个demo,点击不同的导航,内容区相应切换,并且history推入一条记录,可实现浏览器的后退和书签保存功能。
//html<ul class="header"> <li><a href="#nav1">nav1</a></li> <li><a href="#nav2">nav2</a></li> <li><a href="#nav3">nav3</a></li> <li><a href="#nav4">nav4</a></li> <li><a href="#nav5">nav5</a></li></ul><div class="content"> <div style="display: none">nav1内容区域</div> <div style="display: none">nav2内容区域</div> <div style="display: none">nav3内容区域</div> <div style="display: none">nav4内容区域</div> <div style="display: none">nav5内容区域</div></div>
//js var eleMenus = document.querySelector('.header'); function FrontRouter() { window.addEventListener('load', this.refresh, false); if(history.pushState) { window.addEventListener('popstate', this.refresh, false); } } FrontRouter.prototype.refresh = function () { var hash = location.hash; var eleTarget; if(!hash) { eleTarget= eleMenus.querySelector('li a'); history.replaceState(null, null, location.href + '#' + eleTarget.href.split('#')[1]); eleTarget.click(); }else { var eleLinks = eleMenus.querySelectorAll('li a'); for(let i=0, len=eleLinks.length; i<len; i++) { if(eleLinks[i].getAttribute('href') === hash) { eleTarget = eleLinks[i]; } } //未找到重新遍历,递归自身 if(!eleTarget) { history.replaceState(null, null, location.href.split("#")[0]); FrontRouter.prototype.refresh(); }else { eleTarget.click(); } } }; var router = new FrontRouter(); /* * Node原型小方法 */ Object.defineProperties(Node.prototype, { //取元素的当前索引 n_getIndex: { value: function () { if (this.parentElement) { var list = this.parentElement.children; for (var i = 0; i < list.length; i++) { if (list[i] === this) { return i; } } } } }, //取当前元素的同辈元素, 是个数组 n_siblings: { value: function () { var par = this.parentElement; if (par) { var list = par.children, len = list.length, arr = []; for (var i = 0; i < len; i++) { if (list[i] !== this) { arr.push(list[i]); } } return arr; } } }, //判断是否含有class n_hasClass: { value: function (name) { name = ' ' + name.trim() + ' '; return (new RegExp(name, 'm')).test(' ' + this.className + ' '); } }, //添加class n_addClass: { value: function (name) { var curNames = this.className.trim(); name = name.trim(); if (curNames) { if ((' ' + curNames + ' ').indexOf(' ' + name + ' ') === -1) { this.className = curNames + ' ' + name; } } else { this.className = name; } return this; } }, //移除class n_removeClass: { value: function (name) { var curNames = this.className.trim(); if (curNames) { name = ' ' + name.trim() + ' '; this.className = (' ' + curNames + ' ').replace(new RegExp(name, 'gm'), ' ').trim(); } return this; } } }); //nav的点击切换函数 eleMenus.addEventListener('click', function (e) { if(e.target.tagName.toUpperCase() === 'A') { var eleTarget = e.target, liTarget = eleTarget.parentElement; var targetIndex = liTarget.n_getIndex(); if(!liTarget.n_hasClass('selected')) { liTarget.n_addClass('selected'); var _siblings = liTarget.n_siblings(); for(let i=0, len=_siblings.length; i<len; i++) { _siblings[i].n_removeClass('selected'); } var contentLists = document.querySelectorAll('.content div'); for(let i=0, len = contentLists.length; i<len; i++) { contentLists[i].style.display = 'none'; } contentLists[targetIndex].style.display = 'block'; } //push history stack if(history.pushState && location.hash !== eleTarget.getAttribute('href')) { var title = e.target.innerHTML; history.pushState({title: title}, title, location.href.split('#')[0] + '#' +eleTarget.getAttribute('href').substring(1)); } e.preventDefault(); } }, false);
//css <style> *{margin: 0;padding: 0} .header{ width: 500px; height: 50px; margin: 50px auto 0; border: 1px solid #CCCCCC; border-bottom: none; font-size: 0; } .header li { display: inline-block; width: 100px; box-sizing: border-box; border-right: 1px solid #CCCCCC; border-bottom: 1px solid #CCCCCC; font-size: 14px; line-height: 50px; text-align: center; cursor: pointer; } .header li a { display: inline-block; width: 100%; height: 100%; } .header li:last-child{ border-right: none; } .header li.selected{ border-bottom: none; } .header li.selected a { color: blue; } .content { width: 500px; height: 300px; margin: 0 auto; border: 1px solid #CCCCCC; border-top: none; } .content>div{ padding: 50px; } </style>
总结
前端路由主要应用在SPA单页面上,现在很多框架比如Angular、React、Vue都有router插件,大体都是利用这两个方法来实现,当然会复杂很多。个人倾向于hash的方式。history的兼容性要IE9及以上。
这个链接的 demo 含有判断方法:http://sandbox.runjs.cn/show/… 。同时给出 Github 仓库地址: minrouter,推荐大家读下源码,仅仅 117 行,精辟!
- 前端路由相关实现
- 前端路由实现
- history实现前端路由
- 前端路由实现原理
- Javascript实现前端简单路由
- hash模式实现前端路由
- 前端路由的实现原理
- Director JS 实现前端路由
- 用director.js实现的前端路由
- 前端路由的不同方法实现
- 前端路由的两种实现原理
- 前端路由的两种实现原理
- 利用history.pushState实现前端路由
- 前端路由
- 前端路由
- 【前端路由】
- 前端路由
- 前端路由
- WPScan初体验
- zookeeper技术浅析
- HTTP请求方式辨别
- mysql触发器与sqlserver触发器互转
- 对象的内存销毁时间表
- 前端路由相关实现
- SpringMVC上传下载Exacel
- 【不要犯同一错误两次】注意数据类型
- lintcode-简单-判断字符串是否没有重复字符
- Spring data jpa查询多个部分字段的方法
- 新手学习Cartographer(1)-安装与测试
- SSH项目上传图片并把图片路径存放在数据库, 及其图片的显示
- iOS 多线程(一)Pthread
- spydroid源码阅读笔记