前端路由History/Hash原理初窥

在传统的多页面时代,网页都是请求一个url,然后后端生成一个完整的html文档,返回给前端。 每次url改变都意味着页面刷新,重新加载新的html文档。这个时候所有路由都是后端控制的。

随着前端技术的发展,前端可以用ajax请求后端数据,便出现了单页面应用。所谓单页面应用,就是只在开始加载一次html文档,之后的操作都是js完成。其中就包括了路由这个功能。

说起来也挺简单,js监听url的改变,js改变dom。

History

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>Document</title>
  </head>
  <body>
    <ul>
      <li><a href="/">/</a></li>
      <li><a href="/page1">page1</a></li>
      <li><a href="/page2">page2</a></li>
    </ul>
    <div class="content-div"></div>
  </body>
  <script>
    class RouterClass {
      constructor(path) {
        this.routes = {}; // 记录路径标识符对应的cb
        history.replaceState({ path }, null, path); // 进入状态
        this.routes[path] && this.routes[path]();
        window.addEventListener("popstate", e => {// 当用户点击浏览器的前进或者后退触发
            console.log(e.state)
          const path = e.state && e.state.path;
          this.routes[path] && this.routes[path]();
        });
      }
      
      /* 初始化 */
      static init() {
        window.Router = new RouterClass(location.pathname);
      }

      /* 注册路由和回调 */
      route(path, cb) {
        this.routes[path] = cb || function() {};
      }

      /* 跳转路由,并触发路由对应回调 */
      go(path) {
        
        history.pushState({ path }, null, path);
        console.log(history);
        this.routes[path] && this.routes[path]();
      }
    }

    RouterClass.init();
    const ul = document.querySelector("ul");
    const ContentDom = document.querySelector(".content-div");
    const changeContent = content => (ContentDom.innerHTML = content);

    Router.route("/", () => changeContent("默认页面"));
    Router.route("/page1", () => changeContent("page1页面"));
    Router.route("/page2", () => changeContent("page2页面"));

    ul.addEventListener("click", e => {
      console.log(e.target.tagName);
      if (e.target.tagName === "A") {
        e.preventDefault();
        Router.go(e.target.getAttribute("href"));
      }
    });
  </script>
</html>

Hash

URL 片段是哈希标记 # 前面的名称,哈希标记指定当前文档中的内部目标位置(HTML 元素的 ID)。

# 最开始学习html时候,称之为锚点定位,也有叫他页面内部的超链接的。# 后面跟的是元素 id。 如果<a href="#demo"/> 点击页面就会定位 demo 这个元素所在的地方。 另外值得一提的是,#或者top会跳到页面最顶部。

hash 路由就是借用这个 # 号来实现的。因为每次 # 后面的内容改变,都有一个 hashchang 事件。大概原理如下:

function Router(){
    this.routes = [];
    this.currentUrl = '';
}
Router.prototype.add = function(path,callback){
    this.routes[path] = callback || function(){};
}

Router.prototype.refresh = function(){
    this.currentUrl = location.hash.replace(/^#*/,'');
    this.routes[this.currentUrl]();
}
Router.prototype.load = function(){
    window.addEventListener('load',this.refresh.bind(this),false);
    window.addEventListener('hashchange',this.refresh.bind(this),false);
}
Router.prototype.nevigate = function(path){
    path = path ? path : "";
    location.href = location.href.replace(/#(.*)$/,'')+'#'+path;
}
window.Router = new Router();
window.Router.load();



//使用
Router.add('/',function(){
    document.getElementById('demo').innerHTML="Router home!!!";
});
Router.add('/blue',function(){
    document.getElementById('demo').innerHTML="Router blue!!!";
});

当然实际使用需要处理更多情况。

使用 Discussions 讨论 Github 上编辑