前端路由相关实现

来源:互联网 发布:波士顿矩阵作业 编辑:程序博客网 时间: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 行,精辟!

0 0
原创粉丝点击