资讯专栏INFORMATION COLUMN

history和hash,vue-router

godiscoder / 1614人阅读

摘要:另外该事件只针对同一个文档,如果浏览历史的切换,导致加载不同的文档,该事件不会被触发使用的时候,可以为事件指定回调函数或者回调函数的参数是一个事件对象,它的属性指向和方法为当前所提供的状态对象即这两个方法的第一个参数。

history

window.history(可直接写成history)指向History对象,它表示当前窗口的浏览历史。History对象保存了当前窗口访问过的所有页面网址

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

history.pushstate(state,title,url)

if(!!(window.hostory && history.pushState)) {
// 支持History API
} else {
    // 不支持
}

state: 一个与指定网址相关的状态对象,popState事件触发时,该对象会传入回调函数,如果不需要这个对象,此处可填null
title: 新页面的标题,但是所有浏览器目前都忽略这个值,因此这里可以填null
url: 新的网址,必须与当前页面处在同一个域,浏览器的地址栏将显示这个网址

history.pushState({a:1},"page 2","2.html")

用上面代码添加2.html后,浏览器地址栏立刻显示2.html,但不会跳到2.html,只会更新浏览器历史记录,此时点击后退按钮则会回到原网页,但是会改变history的length属性;
如果pushState的url参数,设置了一个新的锚点值(即hash),并不会触发hashChange事件,如果设置了一个跨域网址,则会报错。

replaceState()
history.replaceState()方法的参数和pushState()方法一摸一样,区别是它修改浏览器历史当中的记录
两者的区别在于
push
此时执行history.back()返回/about

replace
此时执行history.back()返回/blog

length
history.length属性保存着历史记录的url数量,初始时该值为1,如果当前窗口先后访问了三个网址,那么history对象就包括3项,history.length=3
state
返回当前页面的state对象。可以通过replaceState()和pushState()改变state,可以存储很多数据
scrollRestoration
history.scrollRestoration = "manual";关闭浏览器自动滚动行为
history.scrollRestoration = "auto";打开浏览器自动滚动行为(默认)
popState 事件
每当同一个文档的浏览历史(即history)出现变化时,就会触发popState事件
需要注意:仅仅调用pushState方法或replaceState方法,并不会触发该事件,只有用户点击浏览器后退和前进按钮时,或者使用js调用back、forward、go方法时才会触发。另外该事件只针对同一个文档,如果浏览历史的切换,导致加载不同的文档,该事件不会被触发
使用的时候,可以为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));
    });

回调函数的参数是一个event事件对象,它的state属性指向pushState和replaceState方法为当前url所提供的状态对象(即这两个方法的第一个参数)。上边代码中的event.state就是通过pushState和replaceState方法为当前url绑定的state对象
这个state也可以直接通过history对象读取
history.state
注意:页面第一次加载的时候,浏览器不会触发popState事件

hash

hash 就是指 url 尾巴后的 # 号以及后面的字符。这里的 # 和 css 里的 # 是一个意思。hash 也 称作 锚点,本身是用来做页面定位的,她可以使对应 id 的元素显示在可视区域内。

通过window.location.hash获取hash值

延伸:
window.location对象里面
hash : 设置或返回从 (#) 开始的 URL(锚)。
host : 设置或返回主机名和当前 URL 的端口号。
hostname:设置或返回当前 URL 的主机名。
href : 设置或返回完整的 URL。
pathname: 设置或返回当前 URL 的路径部分。
port:设置或返回当前 URL 的端口号。
search : 设置或返回从问号 (?) 开始的 URL(查询部分)。
assign() : 加载新的文档。
reload() : 重新加载当前文档。
replace() : 用新的文档替换当前文档。
hashchange

当hash值改变时会触发这个事件,
if("onhashchange" in window) {
   window.addEventListener("hashchange",function(e){
    console.log(e.newURL,e.oldURL)
},false)
}
vue-router

在vue-router中,它提供mode参数来决定采用哪一种方式;
默认是hash,可以配置mode:history,选择history模式;
选好mode后 vueRouter中会创建history对象(HashHistory或HTML5History,这两种类都是继承History类,这个类定义了一些公共方法)

// 根据mode确定history实际的类并实例化
    switch (mode) {
      case "history":
        this.history = new HTML5History(this, options.base)
        break
      case "hash":
        this.history = new HashHistory(this, options.base, this.fallback)
        break
      case "abstract":
        this.history = new AbstractHistory(this, options.base)
        break
      default:
        if (process.env.NODE_ENV !== "production") {
          assert(false, `invalid mode: ${mode}`)
        }
    }
  }

现在我们来看当我们在代码中执行了this.$router.push()之后具体的流程

首先看HashHistory
1 $router.push() //显式调用方法
2 HashHistory.push() // 我们来看下push方法

push (location: RawLocation, onComplete?: Function, onAbort?: Function) {
  this.transitionTo(location, route => {
    pushHash(route.fullPath)
    onComplete && onComplete(route)
  }, onAbort)
}

function pushHash (path) {
  window.location.hash = path
}

transitionTo()方法是父类中定义的是用来处理路由变化中的基础逻辑的,push()方法最主要的是对window的hash进行了直接赋值:hash的改变会自动添加到浏览器的访问历史记录中。

window.location.hash = route.fullPath //类似/thunder/bless_sort/1?fromType=homeTap

那么视图的更新是怎么实现的呢,我们来看父类History中transitionTo()方法的这么一段:

transitionTo (location: RawLocation, onComplete?: Function, onAbort?: Function) {
  const route = this.router.match(location, this.current)
  this.confirmTransition(route, () => {
    this.updateRoute(route)
    ...
  })
}

updateRoute (route: Route) {
  
  this.cb && this.cb(route)
  
}

listen (cb: Function) {
  this.cb = cb
}

路由变化后会执行updateRoute(),其实是执行this.cb ,而 this.cb是在listen函数中被执行的,那么在那里调用listen函数呢

init (app: any /* Vue component instance */) {
    
  this.apps.push(app)

  history.listen(route => {
    this.apps.forEach((app) => {
      app._route = route
    })
  })
}

app为vue组件实例,vue本身是没有vue-routerd的,需要在组件中挂载这个属性

export function install (Vue) {
  
  Vue.mixin({
    beforeCreate () {
      if (isDef(this.$options.router)) {
        this._router = this.$options.router
        this._router.init(this)
        Vue.util.defineReactive(this, "_route", this._router.history.current)
      }
      registerInstance(this, this)
    },
  })
}

通过Vue.mixin()方法,全局注册一个混合,影响注册之后所有创建的每个 Vue 实例,该混合在beforeCreate钩子中通过Vue.util.defineReactive()定义了响应式的_route属性(当前的路由)。即当_route值改变时,会自动调用Vue实例的render()方法,更新视图。
总结一下,从设置路由改变到视图更新的流程如下:

$router.push() --> HashHistory.push() --> History.transitionTo() --> History.updateRoute() --> {app._route = route} --> vm.render()

replace方法
功能: 替换当前路由并更新视图,常用情况是地址栏直接输入新地址
流程与push基本一致
但流程2变为替换当前hash (window.location.replace= XXX)
replace和hash的区别在于它并不是将新路由添加到浏览器访问历史的栈顶,而是替换掉当前的路由:如上图

监听地址栏
以上讨论的VueRouter.push()和VueRouter.replace()是可以在vue组件的逻辑代码中直接调用的,除此之外在浏览器中,用户还可以直接在浏览器地址栏中输入改变路由,因此VueRouter还需要能监听浏览器地址栏中路由的变化,并具有与通过代码调用相同的响应行为。在HashHistory中这一功能通过setupListeners实现:

setupListeners () {
  window.addEventListener("hashchange", () => {
    if (!ensureSlash()) {
      return
    }
    this.transitionTo(getHash(), route => {
      replaceHash(route.fullPath)
    })
  })
}

该方法设置监听了浏览器事件hashchange,调用的函数为replaceHash,即在浏览器地址栏中直接输入路由相当于代码调用了replace()方法

而在HTML5History具体又是怎样的呢
代码结构以及更新视图的逻辑与hash模式基本类似,只不过将对window.location.hash直接进行赋值window.location.replace()改为了调用history.pushState()和history.replaceState()方法。
在HTML5History中添加对修改浏览器地址栏URL的监听是直接在构造函数中执行的:监听popState事件(地址栏变化触发window.onpopstate),调用repalce方法

 constructor (router: Router, base: ?string) {
      
      window.addEventListener("popstate", e => {
        const current = this.current
        this.transitionTo(getLocation(this.base), route => {
          if (expectScroll) {
            handleScroll(router, route, current, true)
          }
        })
      })
    }

除此之外vue-router还为非浏览器环境准备了一个abstract模式,其原理为用一个数组stack模拟出浏览器历史记录栈的功能。以上是vue-router的核心逻辑;

两种模式对比
History模式的优点:
1.History模式的地址栏更美观。。。
2.History模式的pushState、replaceState参数中的新URL可为同源的任意URL(可为不同的html文件),而hash只能是同一文档
3.History模式的pushState、replaceState参数中的state可为js对象,能携带更多数据
4.History模式的pushState、replaceState参数中的title能携带字符串数据(当然,部分浏览器,例如firefox不支持title,一般title设为null,不建议使用)
缺点:
不过这种模式需要后端配置,因为我们这个页面是单页面应用,如果用户直接访问http://oursite.com/user/id
后台没有正确的配置,则就会返回404,
这个时候需要后台配置一个能够覆盖所有情况的候选资源,如果url匹配不到任何静态资源时,则要返回同一个index.html;

注:该篇文章参考了https://zhuanlan.zhihu.com/p/...
转载请注明作者 : crystal 我在桌上刻个早字 谢谢啦

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/104709.html

相关文章

  • vue-router实现原理

    摘要:我们知道是的核心插件,而当前项目一般都是单页面应用,也就是说是应用在单页面应用中的。原理是传统的页面应用,是用一些超链接来实现页面切换和跳转的其实刚才单页面应用跳转原理即实现原理实现原理原理核心就是更新视图但不重新请求页面。 近期面试,遇到关于vue-router实现原理的问题,在查阅了相关资料后,根据自己理解,来记录下。我们知道vue-router是vue的核心插件,而当前vue项目...

    vibiu 评论0 收藏0
  • 简述vue-router实现原理

    摘要:源码解读阅读请关注下代码注释打个广告哪位大佬教我下怎么排版啊,不会弄菜单二级导航扑通是什么首先,你会从源码里面引入,然后再传入参数实例化一个路由对象源码基础类源码不选择模式会默认使用模式非浏览器环境默认环境根据参数选择三种模式的一种根据版 router源码解读 阅读请关注下代码注释 打个广告:哪位大佬教我下sf怎么排版啊,不会弄菜单二级导航(扑通.gif) showImg(https:...

    Cristalven 评论0 收藏0
  • 简述vue-router实现原理

    摘要:源码解读阅读请关注下代码注释打个广告哪位大佬教我下怎么排版啊,不会弄菜单二级导航扑通是什么首先,你会从源码里面引入,然后再传入参数实例化一个路由对象源码基础类源码不选择模式会默认使用模式非浏览器环境默认环境根据参数选择三种模式的一种根据版 router源码解读 阅读请关注下代码注释 打个广告:哪位大佬教我下sf怎么排版啊,不会弄菜单二级导航(扑通.gif) showImg(https:...

    Ajian 评论0 收藏0

发表评论

0条评论

godiscoder

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<