前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >前端路由相关实现

前端路由相关实现

作者头像
空空云
发布2018-09-27 11:46:59
5450
发布2018-09-27 11:46:59
举报
文章被收录于专栏:大前端_Web大前端_Web

版权声明:本文为吴孔云博客原创文章,转载请注明出处并带上链接,谢谢。 https://cloud.tencent.com/developer/article/1347525

前端路由:客户端浏览器可以不依赖服务端,根据不同的URL渲染不同的视图页面。

前端路由实现思路

  • 在页面不刷新的前提下实现url变化
  • 捕捉到url的变化,以便执行页面替换逻辑

前端路由实现方法

HASH

我们经常在 url 中看到 #,这个 # 有两种情况,一个是我们所谓的锚点,比如典型的回到顶部按钮原理、Github 上各个标题之间的跳转等,路由里的 # 不叫锚点,我们称之为 hash,大型框架的路由系统大多都是哈希实现的。

同样我们需要一个根据监听哈希变化触发的事件 —— hashchange 事件

我们用 window.location 处理哈希的改变时不会重新渲染页面,而是当作新页面加到历史记录(session history)中,这样我们跳转页面就可以在 hashchange 事件中注册 ajax 从而改变页面内容。

利用hash值前端路由的简单实现

代码语言:javascript
复制
//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>
代码语言:javascript
复制
//js

function 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。

代码语言:javascript
复制
(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推入一条记录,可实现浏览器的后退和书签保存功能。

代码语言:javascript
复制
//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>
代码语言:javascript
复制
//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);
代码语言:javascript
复制
//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 行,精辟!

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2017年02月16日,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前端路由实现思路
  • 前端路由实现方法
    • HASH
      • history API
      • 总结
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档