专栏首页大前端_Web前端路由相关实现

前端路由相关实现

版权声明:本文为吴孔云博客原创文章,转载请注明出处并带上链接,谢谢。 https://blog.csdn.net/wkyseo/article/details/55255056

前端路由:客户端浏览器可以不依赖服务端,根据不同的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>
//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。

(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 条评论
登录 后参与评论

相关文章

  • js对象属性的getter和setter

    版权声明:本文为吴孔云博客原创文章,转载请注明出处并带上链接,谢谢。 https://blog.csdn.net/wkyseo/articl...

    空空云
  • JS自定义事件原生

    空空云
  • 浅谈 JS 创建对象的 8 种模式

    版权声明:本文为吴孔云博客原创文章,转载请注明出处并带上链接,谢谢。 https://blog.csdn.net/wkyseo/articl...

    空空云
  • Javascript/ES6语法快速查询

    这是一个 ES2015(ES6) 的Cheatsheet,其中包括提示、小技巧、最佳实践和一些代码片段,帮助您 完成日复一日的开发工作。

    mojocn
  • C++代码设计:向Java借鉴Builder模式塈OpenCL内核代码编译

    版权声明:本文为博主原创文章,转载请注明源地址。 https://blog.csdn.net...

    用户1148648
  • JavaScript中call,apply,bind方法的使用及原理

    在JavaScript里,call(),apply(),bind()都是Function内置的三个方法, 它们的作用都是显示的绑定this的指向,三个方法的第一...

    伯爵
  • JavaScript语言精粹【语法、对象、函数】

    版权声明:本文为博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。

    奋飛
  • 几个非常实用的JQuery代码片段

    jQuery是一个兼容多浏览器的javascript库,核心理念是write less,do more(写得更少,做得更多)。jQuery使用户能更方便地处理H...

    前端博客 : alili.tech
  • 如何用代码控制浏览器下载知乎大v的粉丝数据?

    欢迎用户在后台留言需解答的问题, mixlab 将会不定期的从中选择提供解决方案。同时 mixlab 微信群已经汇集了机器学习、自然语言处理、前端、后端、产品经...

    mixlab
  • JavaScript的自定义对象

    var obj1 = new Object(), obj2 = {};//Object 对象

    小小鱼儿小小林

扫码关注云+社区

领取腾讯云代金券