前端路由原理

vue-router—前端路由的实现原理

参考

  • https://www.cnblogs.com/FHC1994/p/12408313.html
  • https://www.cnblogs.com/tugenhua0707/p/10859214.html#_labe2

一、前端路由和后端路由是什么??

  • 前端路由

    前端路由是直接找到与地址匹配的一个组件或对象并将其渲染出来。也就是说,通过改变浏览器地址URL,在不重新请求页面的情况下,更新页面视图

  • 后端路由

    浏览器在地址栏中切换不同的url时,每次都向后台服务器发出请求,服务器响应请求,在后台拼接html文件传给前端显示, 返回不同的页面,

    意味着浏览器会刷新页面,网速慢的话说不定屏幕全白再有新内容。

二、 单页面应用和多页面应用

  • 单页面应用

    • 即 第一次进入页面的时候会请求一个html文件,刷新清除一下。切换到其他组件,此时路径也相应变化,但是并没有新的html文件请求,页面内容也变化了
    • 原理:JS会感知到url的变化,通过这一点,可以用js动态的将当前页面的内容清除掉,然后将下一个页面的内容挂载到当前页面上,这个时候的路由不是后端来做了,而是前端来做,判断页面到底是显示哪个组件,清除不需要的,显示需要的组件。这种过程就是单页应用,每次跳转的时候不需要再请求html文件了
  • 多页面应用

    • 每一次页面跳转的时候,后台服务器都会给返回一个新的html文档,这种类型的网站也就是多页网站,也叫做多页应用。

    • 原理:用一些超链接来实现页面切换和跳转的

三、实现原理

vue-router 提供了三种模式来实现前端路由:1.hash模式 2.history模式 3.abstract模式。hash模式与history模式,这两种模式都是通过浏览器接口实现的,除此之外vue-router还为非浏览器环境准备了一个abstract模式,其原理为用一个数组stack模拟出浏览器历史记录栈的功能。这里主要讲解hash模式和history模式实现前端路由的原理。

1.hash模式:监听浏览器地址hash值变化,执行相应的js改变网页内容

  • 本身以及它后面的字符称作hash,可通过window.location.hash属性读取
  • url中可以带有一个hash,比如 http://localhost:9000/#/song 中的hash值就是 #/song
  • 监听hashchange事件触发:
    • 直接更改浏览器地址,在最后面增加或者改变#hash
    • 通过改变locatin.href或者location.hash的值(window.location.href:表示重定向,后面跟着的是完整的url地址。window.location.hash:得到的是锚链接)
    • 通过触发点击带锚点的链接、通过a标签,设置href属性,当点击a标签之后,地址栏会改变
    • 浏览器前进后退可能导致hash的变化,前提是两个网页地址中的hash值不同

知道上面的知识后我们看下实现其应该满足的条件

​ 1. url中hash值的改变,并不会重新加载页面。

​ 2. hash值的改变会在浏览器的访问历史中增加一条记录,我们可以通过浏览器的后退,前进按钮控制hash值的切换。
3. 我们可以通过hashchange事件,监听到hash值的变化,从而加载不同的页面显示。

代码实现(用a标签)

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>hash路由demo</title>
</head>
<body>
  <h1>通过下面的a标签来进行hash切换</h1>
  <ul>
    <li><a href="#/">我是主页蓝色</a></li>
    <li><a href="#/a">我是a绿色页面</a></li>
    <li><a href="#/b">我是b粉色页面</a></li>
  </ul>
  <script>
    class HashRouter {
      constructor() {
        // 存储hash与callback键值对
        this.routes = {};
        // 保存当前的hash
        this.currentHash = '';
        // 绑定事件
        const hashChangeUrl = this.hashChangeUrl.bind(this);

        // 页面加载事件
        window.addEventListener('load', hashChangeUrl, false);
        // 监听hashchange事件
        window.addEventListener('hashchange', hashChangeUrl, false);
      }
      // path路径和callback函数对应起来,并且使用 上面的this.routes存储起来
      route(path, callback) {
        this.routes[path] = callback || function () { };
      }
      hashChangeUrl() {
        /*
         获取当前的hash值
         location.hash 获取的值为:"#/a, 因此 location.hash.slice(1) = '/a' 这样的
        */
        this.currentHash = location.hash.slice(1) || '/';
        // 执行当前hash对应的callback函数
        this.routes[this.currentHash]();
      }
    }
    // 初始化
    const Router = new HashRouter();
    const body = document.querySelector('body');
    const changeColor = function (color) {
      body.style.backgroundColor = color;
    };
    // 注册函数
    Router.route('/', () => {
      changeColor('lightblue');
    });
    Router.route('/a', () => {
      changeColor('lightgreen');
    });
    Router.route('/b', () => {
      changeColor('pink');
    });
  </script>
</body>
</html>

代码实现(用点击用location.hash来实现)

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <style>
    ul {
      margin: 0;
      padding: 0;
      list-style: none;
    }
    li {
      width: 20px;
      height: 20px;
      background-color: lightblue;
      margin-bottom: 10px;
    }
  </style>
</head>
<body>
  <h1>点击下列标签实现修改url history(hash方式)</h1>
  <ul>
    <li>1</li>
    <li>2</li>
    <li>3</li>
  </ul>
  <script>
    let liObj = document.getElementsByTagName('li')
    ;[...liObj].forEach(item => {
      item.addEventListener('click',() => {
        location.hash = item.textContent
      },false)
    })

    //监听路由的变化
    window.addEventListener('hashchange', () => {
        alert(location.hash.slice(1))
    },false)
  </script>
</body>
</html>

2.history模式:利用history API实现url地址改变,网页内容改变

  • History 对象是 window 对象的一部分,可通过 window.history 属性对其进行访问。HTML5 History API提供了一种功能,能让开发人员在不刷新整个页面的情况下修改站点的URL,就是利用 history.pushState API 来完成 URL 跳转而无须重新加载页面。

  • History对象方法:

    • history.back() #加载 history 列表中的前一个 URL
    • history.forward() #加载 history 列表中的下一个 URL]
    • history.go(n) #加载 history 列表中的某个具体页面
  • HTML5新接口:history.pushState(stateObj,title,url)

    • window.history.pushState(stateObj, title, targetURL);
      @状态对象:传给目标路由的信息,可为空
      @页面标题:目前所有浏览器都不支持,填空字符串即可
      @可选url:目标url,不会检查url是否存在,且不能跨域。如不传该项,即给当前url添加sateObj
      
    • pushState方法不会触发页面刷新,只是导致history对象发生变化,改变网址,网页不会真的跳转,本质上网页还停留在原页面

    • 这里的url受到同源策略的限制

  • popstate事件触发:每当同一个文档的浏览历史(即history对象)出现变化时,就会触发popstate事件

    • popstate事件会在点击后退、前进按钮(或调用history.back()history.forward()history.go()方法)时触发。**由history.pushState()或者history.replaceState()形成的历史节点中前进后退会形成页面切换。**注意:仅仅调用pushState方法或replaceState方法,并不会触发该事件,只有用户点击浏览器倒退按钮 和前进按钮,或者使用JavaScript调用back、forward、go方法时才会触发。另外,该事件只针对同一个文档,如果浏览历史的切换,导致加载不同的文档,该事件也不会触发
    • 用法:使用的时候,可以为popstate事件指定回调函数。这个回调函数的参数是一个event事件对象,它的state属性指向pushState和replaceState方法为当前URL所提供的状态对象(即这两个方法的第一个参数)
  • 代码实现

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title>History实现前端路由的原理</title>
    </head>
    <body>
        <!-- 跳转组件是由a标签实现  -->
        <a href="/1" class="spa">1</a>
        <a href="/2" class="spa">2</a>
        <a href="/3" class="spa">3</a>
        <script>
            //监听a标签
            //querySelectorAll找出所有匹配的节点并返回数组.
            document.querySelectorAll(".spa").forEach(item => {
                item.addEventListener("click",e=>{
                    // 阻止a标签的默认事件
                    e.preventDefault();
                    //帮助改变url地址
                    let link = item.textContent  //获取文本内容
                    
                    // 检查是否支持history和pushState
                    if(window.history && window.history.pushState){
                        //支持History API
                        window.history.pushState({name:"history"},link,link)
                    }else{
                        //不支持,可以使用一些Ployfill插件库来实现(Polyfill 是一块代码(通常是 Web 上的 JavaScript),用来为旧浏览器提供它没有原生支持的较新的功能。)
                    }
                },false)
            })  
            
            //监听路由
            window.addEventListener('popstate',e=>{
                console.log({
                    location:location.href,
                    state:e.state
                })
            })
            
        </script>
    </body>
</html>

本地运行可能报错,不过可以试试在随便打开一个页面 用history.pushState()试试,页面还是无刷新的

四、history模式的一个问题

  • history api,我们丢掉了丑陋的#,但是它也有个问题:不怕前进,不怕后退,就怕刷新

  • 对于单页应用来讲,理想的使用场景是仅在进入应用时加载index.html,后续的网络操作通过Ajax完成,不会根据URL重新请求页面,但是难免遇到特殊情况,比如用户直接在地址栏输入并回车,浏览器重启重新加载应用等。

  • 简单说明

    • 在hash模式下,对于http://oursite.com/#/user/id 如果重新请求,只会发送http://oursite.com 故在hash模式下遇到根据URL请求页面的情况不会有问题。hash模式仅改变hash部分的内容,而hash部分是不会包含在HTTP请求中的
    • 在history模式下,对于http://oursite.com/#/user/id 会将该URL修改得和正常请求后端的URL一样,在此情况下重新向后端发送请求,如后端没有配置对应/user/id的路由处理,则会返回404错误
  • 解决办法:官方推荐的解决办法是在服务器端增加一个覆盖所有情况的候选资源:如果URL匹配不到任何静态资源,则应该返回一个index.html页面,这个页面就是你app依赖的页面。同时这么做以后,服务器就不会再返回404错误页面,因为对于所有路径都会返回index.html文件。为了避免这种情况,在Vue应用里面覆盖所有的路由情况,然后在给出一个404页面。

posted @ 2020-07-09 20:15  fcslow  阅读(406)  评论(0)    收藏  举报