history和hash,vue-router

history

window.history(可直接寫成history)指向History對象,它表示當前窗口的瀏覽歷史。History對象保存了當前窗口訪問過的全部頁面網址css

  1. history對象的常見屬性和方法
    go()
    接受一個整數爲參數,移動到該整數指定的頁面,好比history.go(1)至關於history.forward(),history.go(-1)至關於history.back(),history.go(0)至關於刷新當前頁面
    back()
    移動到上一個訪問頁面,等同於瀏覽器的後退鍵,常見的返回上一頁就能夠用back(),是從瀏覽器緩存中加載,而不是從新要求服務器發送新的網頁
    forward()
    移動到下一個訪問頁面,等同於瀏覽器的前進鍵
    pushState()
    在瀏覽器歷史中添加記錄,方法接受三個參數,以此爲:html

    history.pushstate(state,title,url)
    
    if(!!(window.hostory && history.pushState)) {
    // 支持History API
    } else {
        // 不支持
    }

    state: 一個與指定網址相關的狀態對象,popState事件觸發時,該對象會傳入回調函數,若是不須要這個對象,此處可填null
    title: 新頁面的標題,可是全部瀏覽器目前都忽略這個值,所以這裏能夠填null
    url: 新的網址,必須與當前頁面處在同一個域,瀏覽器的地址欄將顯示這個網址vue

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

    用上面代碼添加2.html後,瀏覽器地址欄馬上顯示2.html,但不會跳到2.html,只會更新瀏覽器歷史記錄,此時點擊後退按鈕則會回到原網頁,可是會改變history的length屬性;
    若是pushState的url參數,設置了一個新的錨點值(即hash),並不會觸發hashChange事件,若是設置了一個跨域網址,則會報錯。vue-router

    replaceState()
    history.replaceState()方法的參數和pushState()方法一摸同樣,區別是它修改瀏覽器歷史當中的記錄
    二者的區別在於
    push
    此時執行history.back()返回/about
    clipboard.png後端

    replace
    此時執行history.back()返回/blog
    clipboard.png跨域

    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 我在桌上刻個早字 謝謝啦

相關文章
相關標籤/搜索