浅析vue-router的三种模式

面试官: 请说一下vue-router的2种模式...

我: vue-router不是有3种模式吗???

一.前言

vue-router到底有几种模式?

依据vue-router官网,咱们能够明确看到vue-router的mode值有3种

hash
history
abstract

其中,hash 和 history 是 SPA 单页应用程序的根底。

先说论断: spa利用路由有2种模式,hash 和 history,vue路由有3种模式,比 spa 多了一个 abstract。

二.源码剖析

在vue-router中通过mode这个参数批改路由的模式:

const router = new VueRouter({  mode: 'history',  routes: [...]})

具体怎么实现的呢,首先咱们下载 vue-router 的源码

抽离进去对mode的解决

class vueRouter {    constructor(options) {        let mode = options.mode || 'hash'        this.fallback =        mode === 'history' && !supportsPushState && options.fallback !== false        if (this.fallback) {            mode = 'hash'        }        if (!inBrowser) {            mode = 'abstract'        }        this.mode = mode        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}`)                }        }    }}

能够看到默认应用的是 hash 模式,当设置为 history 时,如果不反对 history 办法,也会强制应用 hash 模式。
当不在浏览器环境,比方 node 中时,间接强制应用 abstract 模式。

hash模式

浏览这部分源码前,咱们先来理解下 hash 的根底:
依据MDN上的介绍,Location 接口的 hash 属性返回一个 USVString,其中会蕴含URL标识中的 '#' 和 前面URL片段标识符,'#' 和前面URL片段标识符被称为 hash。
它有这样一些特点:

  1. 在第一个#前面呈现的任何字符,都会被浏览器解读为地位标识符。这意味着,这些字符都不会被发送到服务器端。
  2. 单单扭转#后的局部,浏览器只会滚动到相应地位,不会从新加载网页。
  3. 每一次扭转#后的局部,都会在浏览器的拜访历史中减少一个记录,应用"后退"按钮,就能够回到上一个地位。
  4. 可通过window.location.hash属性读取 hash 值,并且 window.location.hash 这个属性可读可写。
  5. 应用 window.addEventListener("hashchange", fun) 能够监听 hash 的变动

理解了这些基本知识后,咱们持续来看 vue-router 源码对 /src/history/hash.js 的解决

    const handleRoutingEvent = () => {      const current = this.current      if (!ensureSlash()) {        return      }      this.transitionTo(getHash(), route => {        if (supportsScroll) {          handleScroll(this.router, route, current, true)        }        if (!supportsPushState) {          replaceHash(route.fullPath)        }      })    }    const eventType = supportsPushState ? 'popstate' : 'hashchange'    window.addEventListener(      eventType,      handleRoutingEvent    )    this.listeners.push(() => {      window.removeEventListener(eventType, handleRoutingEvent)    })

首先也是应用 window.addEventListener("hashchange", fun) 监听路由的变动,而后应用 transitionTo 办法更新视图

  push (location: RawLocation, onComplete?: Function, onAbort?: Function) {    const { current: fromRoute } = this    this.transitionTo(      location,      route => {        pushHash(route.fullPath)        handleScroll(this.router, route, fromRoute, false)        onComplete && onComplete(route)      },      onAbort    )  }  replace (location: RawLocation, onComplete?: Function, onAbort?: Function) {    const { current: fromRoute } = this    this.transitionTo(      location,      route => {        replaceHash(route.fullPath)        handleScroll(this.router, route, fromRoute, false)        onComplete && onComplete(route)      },      onAbort    )  }

vue-router 的2个次要API push 和 replace 也是简略解决了下 hash , 而后调用 transitionTo 办法更新视图

history模式

老规矩,先来理解下 HTML5History 的的基本知识:
依据MDN上的介绍,History 接口容许操作浏览器的已经在标签页或者框架里拜访的会话历史记录。
应用 back(), forward()和 go() 办法来实现在用户历史记录中向后和向前的跳转。
HTML5引入了 history.pushState() 和 history.replaceState() 办法,它们别离能够增加和批改历史记录条目。
略微理解下 history.pushState():

window.onpopstate = function(e) {   alert(2);}let stateObj = {    foo: "bar",};history.pushState(stateObj, "page 2", "bar.html");

这将使浏览器地址栏显示为 http://mozilla.org/bar.html ,但并不会导致浏览器加载 bar.html ,甚至不会查看bar.html 是否存在。
也就是说,尽管浏览器 URL 扭转了,但不会立刻从新向服务端发送申请,这也是 spa利用 更新视图但不 从新申请页面的根底。
接着咱们持续看 vue-router 源码对 /src/history/html5.js 的解决:

    const handleRoutingEvent = () => {      const current = this.current      // Avoiding first `popstate` event dispatched in some browsers but first      // history route not updated since async guard at the same time.      const location = getLocation(this.base)      if (this.current === START && location === this._startLocation) {        return      }      this.transitionTo(location, route => {        if (supportsScroll) {          handleScroll(router, route, current, true)        }      })    }    window.addEventListener('popstate', handleRoutingEvent)    this.listeners.push(() => {      window.removeEventListener('popstate', handleRoutingEvent)    })

解决逻辑和 hash 类似,应用 window.addEventListener("popstate", fun) 监听路由的变动,而后应用 transitionTo 办法更新视图。
push 和 replace 等办法就不再具体介绍。

abstract模式

最初咱们间接来看一下对 /src/history/abstract.js 的解决:

  constructor (router: Router, base: ?string) {    super(router, base)    this.stack = []    this.index = -1  }

首先定义了2个变量,stack 来记录调用的记录, index 记录以后的指针地位

  push (location: RawLocation, onComplete?: Function, onAbort?: Function) {    this.transitionTo(      location,      route => {        this.stack = this.stack.slice(0, this.index + 1).concat(route)        this.index++        onComplete && onComplete(route)      },      onAbort    )  }  replace (location: RawLocation, onComplete?: Function, onAbort?: Function) {    this.transitionTo(      location,      route => {        this.stack = this.stack.slice(0, this.index).concat(route)        onComplete && onComplete(route)      },      onAbort    )  }

push 和 replac办法 也是通过 stack 和 index 2个变量,模拟出浏览器的历史调用记录。

三.总结

终于到了最初的总结阶段了:

  1. hash 和 history 的应用形式差不多,hash 中路由带 # ,然而应用简略,不须要服务端配合,站在技术角度讲,这个是配置最简略的模式,自己感觉这也是 hash 被设为默认模式的起因
  2. history 模式须要服务端配合解决404的状况,然而路由中不带 # ,比 hash 好看一点。
  3. abstract 模式没有应用浏览器api,能够放到node环境或者桌面利用中, 自己感觉是对 spa利用 的兜底和能力扩大。

如果有谬误或者不谨严的中央,请务必给予斧正,非常感激。如果喜爱或者有所启发,欢送star github ,对作者也是一种激励。