# history模式和hash模式

前端随着 ajax 的流行,数据请求可以在不刷新浏览器的情况下进行。异步交互体验中最盛行的就是 SPA —— 单页应用。单页应用不仅仅是在页面交互时无刷新的,连页面跳转都是无刷新的,为了实现单页应用,所以就有了前端路由。

# hash模式

#的涵义

  • #代表网页中的一个位置,右面的字符就是代表的位置
  • http://localhost:8081/cbuild/index.html#first就代表网页index.html的first位置。浏览器读取这个URL后,会自动将first位置滚动至可视区域。

#后的字符

  • 在第一个#后面出现的任何字符,都会被浏览器解读为位置标识符。这意味着,这些字符都不会被发送到服务器端。

  • 比如,下面URL的原意是指定一个颜色值:http://www.example.com/?color=#fff但是,浏览器实际发出的请求是"#fff"被省略了。只有将#转码为%23,浏览器才会将其作为实义字符处理。也就是说,上面的网址应该被写成:http://example.com/?color=%23fff

改变#不触发网页重载

  • 单单改变#后的部分,浏览器只会滚动到相应位置,不会重新加载网页。
  • 比如,从http://www.example.com/index.html#location1改成http://www.example.com/index.html#location2浏览器不会重新向服务器请求index.html。

改变#会改变浏览器的访问历史

  • 每一次改变#后的部分,都会在浏览器的访问历史中增加一个记录,使用"后退"按钮,就可以回到上一个位置。

window.location.hash读取#值

  • window.location.hash这个属性可读可写。读取时,可以用来判断网页状态是否改变;写入时,则会在不重载网页的前提下,创造一条访问历史记录。

onhashchange事件

  • 这是一个HTML 5新增的事件,当#值发生变化时,就会触发这个事件。
  • window.addEventListener("hashchange",func, false);

思路

当URL的片段标识符更改时,将触发hashchange事件 (跟在#符号后面的URL部分,包括#符号),然后根据hash值做些路由跳转处理的操作.具体参数可以访问location查看

<!-- hashchange 触发页面改变 -->
	<ul>
      <li>
        <a href="#/a">a</a>
      </li>
      <li>
        <a href="#/b">b</a>
      </li>
      <li>
        <a href="#/c">c</a>
      </li>
    </ul>
    <div id="view"></div>

    <script>
      var view = null;
      // 页面加载完不会触发 hashchange,这里主动触发一次 hashchange 事件
      // 该事件快于onLoad,所以需要在这里操作
      window.addEventListener('DOMContentLoaded', function () {
        view = document.querySelector('#view');
        viewChange();
      });
      // 监听路由变化
      window.addEventListener('hashchange', viewChange);

      // 渲染视图
      function viewChange() {
        switch (location.hash) {
          case '#/b':
            view.innerHTML = 'b';
            break;
          case '#/c':
            view.innerHTML = 'c';
            break;
          default:
            view.innerHTML = 'a';
            break;
        }
      }
</script>

# history模式

window.history(可直接写成history)指向History对象,它表示当前窗口的浏览历史。

History对象保存了当前窗口访问过的所有页面网址

# length

  • history.length属性保存着历史记录的url数量,初始时该值为1,如果当前窗口先后访问了三个网址,那么history对象就包括3项,history.length=3

跳转方法

  • go() 接受一个整数为参数,移动到该整数指定的页面,比如history.go(1)相当于history.forward(),history.go(-1)相当于history.back(),history.go(0)相当于刷新当前页面
  • back() 移动到上一个访问页面,等同于浏览器的后退键,常见的返回上一页就可以用back(),是从浏览器缓存中加载,而不是重新要求服务器发送新的网页
  • forward() 移动到下一个访问页面,等同于浏览器的前进键

如果移动的位置超出了访问历史的边界,以上三个方法并不报错,而是默默的失败

# history.pushState()

history.pushstate() 方法接受三个参数,以此为:

  • state: 一个与指定网址相关的状态对象,popState事件触发时,该对象会传入回调函数,如果不需要这个对象,此处可填null
  • title: 新页面的标题,但是所有浏览器目前都忽略这个值,因此这里可以填null
  • url: 新的网址,必须与当前页面处在同一个域,浏览器的地址栏将显示这个网址
// 假定当前网址是example.com/1.html,我们使用pushState方法在浏览记录(history对象)中添加一个记录

var stateObj = {foo:'bar'};
history.pushState(stateObj,'page 2','2.html')

添加上边这个新纪录后,浏览器地址栏立刻显示example.com/2.html,但不会跳转到2.html,甚至也不会检查2.html是否存在,它只是成为浏览历史中的新纪录。这时,你在地址栏输入一个新的地址,然后点击了后退按钮,页面的url将显示2.html;你再点击以此后退按钮,url将显示1.html

总之,pushState方法不会触发页面刷新,只是导致了history对象发生变化,地址栏会有反应

如果pushState的url参数,设置了一个新的锚点值(即hash),并不会触发hashChange事件,如果设置了一个跨域网址,则会报错。

//报错
history.pushState(null,null,'https://twitter.com/hello')

上边代码中,pushState()想要插入一个跨域的网址,导致报错,这样设计的目的是防止恶意代码让用户以为他们是在另一个网站上.

# history.replaceState()

history.replaceState() 方法的参数和 pushState() 方法一摸一样,区别是它修改浏览器历史当中的记录

假定当前页面是example.com/example.html

history.pushState({page:1},'title 1','?page=1')
history.pushState({page:2},'title 2','?page=2')
history.replaceState({page:3},'title 3','?page=3')

history.back() //url显示为example.com/example.html?page=1
history.back() //url显示为example.com/example.html
history.go(2) //url显示为example.com/example.html?page=3

# popState 事件

  • history.pushState和history.replaceState方法是不会触发popstate事件的
  • 但是浏览器的某些行为会导致popstate,比如go、back、forward
  • popstate事件对象中的state属性,可以理解是我们在通过history.pushState或history.replaceState方法时,传入的指定的数据,可以为popState事件指定回调函数
window.onpopstate = function (event) {
  console.log('location: ' + document.location);
  console.log('state: ' +JSON.stringify(event.state));
};

// 或者
window.addEventListener('popstate', function(event) {
  console.log('location: ' + document.location);
  console.log('state: ' + JSON.stringify(event.state));
});

所以,我们可以罗列出所有可能触发 history 改变的情况,并且将这些方式一一进行拦截,变相地监听 history 的改变。

对于单页应用的 history 模式而言,url 的改变只能由下面四种方式引起:

  1. 点击浏览器的前进或后退按钮
  2. 点击 a 标签
  3. 在 JS 代码中触发 history.pushState 函数
  4. 在 JS 代码中触发 history.replaceState 函数
<!-- popstate 和点击事件 触发页面改变 -->
	<ul>
      <li>
        <a href="/a">a</a>
      </li>
      <li>
        <a href="/b">b</a>
      </li>
      <li>
        <a href="/c">c</a>
      </li>
    </ul>
    <div id="view"></div>

    <script>
      var view = null;
      
      window.addEventListener('DOMContentLoaded', function () {
        view = document.querySelector('#view');
        document
          .querySelectorAll('a[href]')
          .forEach(e => e.addEventListener('click', function (_e) {
            _e.preventDefault();
            history.pushState(null, '', e.getAttribute('href'));
            viewChange();
          }));

        viewChange();
      });
      // 监听路由变化
      window.addEventListener('popstate', viewChange);

      // 渲染视图
      function viewChange() {
        switch (location.pathname) {
          case '/b':
            view.innerHTML = 'b';
            break;
          case '/c':
            view.innerHTML = 'c';
            break;
          default:
            view.innerHTML = 'a';
            break;
        }
      }
</script>

优点:

  • 该模式的路由不带#,看起来更美观
  • pushState设置的URL可以是任意的与当前URL同源的URL,而hash只能改变#后面的内容

缺点

  • 当我们使用 history 模式路由时,比如有这么一个 url:www.test.com/home,如果我们刷新页面的话,浏览器会发送新的请求 www.test.com/home, 如果后端服务器没有 /home 对应的接口,那么就会返回404。
  • 刷新 404 的解决办法:在服务端增加一个覆盖所有情况的候选资源:如果 URL 匹配不到任何静态资源,接口则应该返回首页 index.html。

# 根据权限动态添加路由列表

  • 业务场景:不同用户调用权限接口获得的权限是不同的,根据用户权限动态展示路由列表
		 api.getAuditInfo({
             account: username,
         }).then(res => {
             console.log('权限代码:', res)
             // 高级权限:可以查看某一块路由
             if (res.includes('auth0')) {
                 window.authInfo = true
                 // 添加路由
                 this.$router.addRoutes(videoRoutes)
                 this.$router.options.routes.push(...videoRoutes)
             // 普通权限
             } else if (res.includes('auth1') && !res.includes('auth0')) {
                 window.authInfo = false
             // 没有权限时返回
             } else {
                 window.location.href = `
             }
         })

# 配置路由meta属性

  • 业务场景:同一个组件在不同路由下的展示状态是不同的,这时可以配置路由meta属性来实现
const routes = [
    {
        path: '/audit/video',
        name: '视频管理',
        component: Layout,
        children: [
            {
                path: 'videoList',
                name: '视频列表',
                meta: {
                    noCache: true,
                    cityChange: true,
                },
                component: auditvideoList
            },
            {
                path: 'auditList',
                name: '审核列表',
                // 配置 meta 属性
                meta: {
                    noCache: true,
                    cityDefault: true 
                },
                component: auditList
            }
        ]
    },
    {
        path: '*',
        redirect: '/audit/agent',
        hidden: true
    }
]
  • 比如说 header 中的一个组件,不同路由页面均展示,只是状态不同
<template>
    <div v-if="!cityDefault"></div>
	  <div v-else ></div>
</template>	
	
<script>
computed: {
        cityDefault() {
            return this.$route.meta.cityDefault;
        }
    },
</script>

# router跳转两次的问题

浏览器会自动对中文进行转码,会导致router的监听方法监听到浏览器地址栏变化。也就是说当我们跳转链接存在中文字符时,浏览器地址会变化两次,然后导致页面组件也会加载两次;

最后更新时间: 2/24/2023, 5:42:01 PM