前端路由的两种实现原理

图片描述

早期的路由都是后端实现的,直接根据 url 来 reload 页面,页面变得愈来愈复杂服务器端压力变大,随着 ajax 的出现,页面实现非 reload 就能刷新数据,也给前端路由的出现奠基了基础。咱们能够经过记录 url 来记录 ajax 的变化,从而实现前端路由。html

本文主要讲两种主流方式实现前端路由。前端

History API

这里不细说每个 API 的用法,你们能够看 MDN 的文档:https://developer.mozilla.org...html5

重点说其中的两个新增的API history.pushStatehistory.replaceStategit

这两个 API 都接收三个参数,分别是github

  • 状态对象(state object) — 一个JavaScript对象,与用pushState()方法建立的新历史记录条目关联。不管什么时候用户导航到新建立的状态,popstate事件都会被触发,而且事件对象的state属性都包含历史记录条目的状态对象的拷贝。
  • 标题(title) — FireFox浏览器目前会忽略该参数,虽然之后可能会用上。考虑到将来可能会对该方法进行修改,传一个空字符串会比较安全。或者,你也能够传入一个简短的标题,标明将要进入的状态。
  • 地址(URL) — 新的历史记录条目的地址。浏览器不会在调用pushState()方法后加载该地址,但以后,可能会试图加载,例如用户重启浏览器。新的URL不必定是绝对路径;若是是相对路径,它将以当前URL为基准;传入的URL与当前URL应该是同源的,不然,pushState()会抛出异常。该参数是可选的;不指定的话则为文档当前URL。

相同之处是两个 API 都会操做浏览器的历史记录,而不会引发页面的刷新。ajax

不一样之处在于,pushState会增长一条新的历史记录,而replaceState则会替换当前的历史记录。后端

咱们拿大百度的控制台举例子(具体说是个人浏览器在百度首页打开控制台。。。)跨域

咱们在控制台输入浏览器

window.history.pushState(null, null, "https://www.baidu.com/?name=orange");

好,咱们观察此时的 url 变成了这样安全

图片描述

咱们这里不一一测试,直接给出其它用法,你们自行尝试

window.history.pushState(null, null, "https://www.baidu.com/name/orange");
//url: https://www.baidu.com/name/orange

window.history.pushState(null, null, "?name=orange");
//url: https://www.baidu.com?name=orange

window.history.pushState(null, null, "name=orange");
//url: https://www.baidu.com/name=orange

window.history.pushState(null, null, "/name/orange");
//url: https://www.baidu.com/name/orange

window.history.pushState(null, null, "name/orange");
//url: https://www.baidu.com/name/orange
注意:这里的 url 不支持跨域,当咱们把 www.baidu.com 换成 baidu.com 时就会报错。
Uncaught DOMException: Failed to execute 'pushState' on 'History': A history state object with URL 'https://baidu.com/?name=orange' cannot be created in a document with origin 'https://www.baidu.com' and URL 'https://www.baidu.com/?name=orange'.

回到上面例子中,每次改变 url 页面并无刷新,一样根据上文所述,浏览器会产生历史记录

图片描述

这就是实现页面无刷新状况下改变 url 的前提,下面咱们说下第一个参数 状态对象

若是运行 history.pushState() 方法,历史栈对应的纪录就会存入 状态对象,咱们能够随时主动调用历史条目

此处引用 mozilla 的例子

<!DOCTYPE HTML>
<!-- this starts off as http://example.com/line?x=5 -->
<title>Line Game - 5</title>
<p>You are at coordinate <span id="coord">5</span> on the line.</p>
<p>
 <a href="?x=6" onclick="go(1); return false;">Advance to 6</a> or
 <a href="?x=4" onclick="go(-1); return false;">retreat to 4</a>?
</p>
<script>
 var currentPage = 5; // prefilled by server!!!!
 function go(d) {
     setupPage(currentPage + d);
     history.pushState(currentPage, document.title, '?x=' + currentPage);
 }
 onpopstate = function(event) {
     setupPage(event.state);
 }
 function setupPage(page) {
     currentPage = page;
     document.title = 'Line Game - ' + currentPage;
     document.getElementById('coord').textContent = currentPage;
     document.links[0].href = '?x=' + (currentPage+1);
     document.links[0].textContent = 'Advance to ' + (currentPage+1);
     document.links[1].href = '?x=' + (currentPage-1);
     document.links[1].textContent = 'retreat to ' + (currentPage-1);
 }
</script>

咱们点击 Advance to ? 对应的 url 与模版都会 +1,反之点击 retreat to ? 就会都 -1,这就知足了 url 与模版视图同时变化的需求

实际当中咱们不须要去模拟 onpopstate 事件,官方文档提供了 popstate 事件,当咱们在历史记录中切换时就会产生 popstate 事件。对于触发 popstate 事件的方式,各浏览器实现也有差别,咱们能够根据不一样浏览器作兼容处理。

hash

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

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

咱们用 window.location 处理哈希的改变时不会从新渲染页面,而是看成新页面加到历史记录中,这样咱们跳转页面就能够在 hashchange 事件中注册 ajax 从而改变页面内容。

这里我在 codepen 上模拟了一下原理: http://codepen.io/orangexc/pe...

hashchange 在低版本 IE 须要经过轮询监听 url 变化来实现,咱们能够模拟以下

(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);

大型框架的路由固然不会这么简单,angular 1.x 的路由对哈希、模版、处理器进行关联,大体以下

app.config(['$routeProvider', '$locationProvider', function ($routeProvider, $locationProvider) {
 $routeProvider
 .when('/article', {
   templateUrl: '/article.html',
   controller: 'ArticleController'
 }).otherwise({
   redirectTo: '/index'
 });
 $locationProvider.html5Mode(true);
}])

这套路由方案默认是以 # 开头的哈希方式,若是不考虑低版本浏览器,就能够直接调用 $locationProvider.html5Mode(true) 利用 H5 的方案而不用哈希方案。

总结

两种方案我推荐 hash 方案,由于照顾到低级浏览器,就是不美观(多了一个 #),二者兼顾也不是不可,只能判断浏览器给出对应方案啦,不过也只支持 IE8+,更低版本兼容见上文!

这个连接的 demo 含有判断方法:http://sandbox.runjs.cn/show/... 。同时给出 Github 仓库地址: minrouter,推荐你们读下源码,仅仅 117 行,精辟!

若是在上面连接测试时你的 url 里多了一个 #,说明你的浏览器该更新啦。

文章出自 orange 的 我的博客 http://orangexc.xyz/
相关文章
相关标签/搜索