容易忽略的URL

場景再現

衆所周知,vue-router有三種模式 :hashhtml5abstract , 通常的前端項目中會選擇hash模式進行開發,最近作了一個運營活動就是基於vue-router的hash模式進行開發的。html

  • 項目註冊了兩個路由(抽象出來的Demo)
var router = new VueRouter({
    routes: [{
        name: 'index',
        path: '',
        component: indexcomponent
    },{
        name: 'demo',
        path: '/demo',
        component: democomponent
    }]
});
複製代碼
  • 入口頁面須要參數,因此提供URL:https://www.xxx.com?from=weixin, 瀏覽器裏輸入URL回車後,頁面自動增長一個#/變爲https://www.xxx.com?from=weixin#/前端

  • index頁面中一個按鈕點擊後跳轉demo,同時想攜帶index中獲取的參數,看API選擇了以下方式,結果URL變成了:https://www.xxx.com?from=weixin#/test?userId=123vue

router.push({ 
    path: 'demo',
    query: { 
        plan: 'private'
    }
})
複製代碼

產生質疑

  • URL有什麼標準?(上面Demo頁面跳轉後URL看起來怪怪的)
  • vue-router是如何控制URL的?

質疑探究

URL標準

統一資源定位符(或稱統一資源定位器/定位地址、URL地址等,英語:Uniform Resource Locator,常縮寫爲URL)html5

標準格式:scheme:[//authority]path[?query][#fragment]git

例子github

下圖展現了兩個 URI 例子及它們的組成部分。vue-router

                    hierarchical part
        ┌───────────────────┴─────────────────────┐
                    authority               path
        ┌───────────────┴───────────────┐┌───┴────┐
  abc://username:password@example.com:123/path/data?key=value&key2=value2#fragid1
  └┬┘   └───────┬───────┘ └────┬────┘ └┬┘           └─────────┬─────────┘ └──┬──┘
scheme  user information     host     port                  query         fragment

  urn:example:mammal:monotreme:echidna
  └┬┘ └──────────────┬───────────────┘
scheme              path

URL中的『?』『#』

  • 『?』segmentfault

    • 路徑與參數分隔符
    • 瀏覽器只識別url中的第一個『?』,後面的會當作參數處理
  • 『#』設計模式

    • 『#』通常是頁面內定位用的,如咱們最熟悉不過的錨點定位
    • 瀏覽器能夠經過『onhashchange』監聽hash的變化
    • http請求中不包含#
    • Request Headers中的Referer不包含#
    • 改變#不觸發網頁重載
    • url中#後面出現的任何字符都會被截斷。(http://www.xxx.com/?color=#fff發出請求是:/color=
    • 改變#會改變history
    • window.location.hash讀取#值

URL讀取和操做

URL讀取和操做涉及location和history兩個對象,具體以下:api

location API :

  • 屬性
    • href = protocol + hostName + port + pathname + search + hash
    • host
    • origin
  • 方法
    • assign
    • href
    • replace ,不記錄history
    • reload

history API:

  • 方法
    • back()
    • forward()
    • go()
  • H5新增API
    • pushState()
    • replaceState()
    • popstate監聽變化

vue-router路由實現淺析

初始化router的時候,根據指定的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模式進行深刻分析,對應HashHistory模塊,該模塊是history/hash.js實現的,當被調用的時候,對全局路由變化進行了監聽

window.addEventListener(supportsPushState ? 'popstate' : 'hashchange', () => {
      ...
})
複製代碼

同時hash.js中也實現了push等api方法的封裝,咱們以push爲例,根據源碼能夠看出,它的實現是基於基類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)
  }
複製代碼

既然調用了transitionTo那麼來看它的實現,獲取參數後調用confirmTransition

transitionTo (location: RawLocation, onComplete?: Function, onAbort?: Function) {
    // 獲取URL中的參數
    const route = this.router.match(location, this.current)
    this.confirmTransition(route, () => {
      this.updateRoute(route)
      onComplete && onComplete(route)
      this.ensureURL()
      ...
    })
  }
複製代碼

同時confirmTransition裏實現了一個隊列,順序執行,iterator經過後執行next,進而志新pushHash(),實現頁面hash改變,最終實現了${base}#${path}的鏈接

function getUrl (path) {
  const href = window.location.href
  const i = href.indexOf('#')
  const base = i >= 0 ? href.slice(0, i) : href
  return `${base}#${path}`
}

function pushHash (path) {
  if (supportsPushState) {
    pushState(getUrl(path))
  } else {
    window.location.hash = path
  }
}
複製代碼

問題解決

  • https://www.xxx.com?from=weixin#/test?userId=123這個頁面看起來感受怪,是由於這個鏈接中幾乎包含了全部的參數,並且hash裏面還有一個問號,一個URL中多個問號的不常見
  • vue-router也是基於基本的URL操做來進行URL切換的,在基本基礎上進行了封裝。裏面不少思路仍是應該多學習借鑑的。好比實現的隊列、繼承的運用等

總結

  • 標準的URL應該是 search + hash ,不要被當下各類框架欺騙,誤以參數應該在hash後面拼接
  • URL中能夠有多個問號,但爲了便於理解,仍是儘可能避免這種寫法
  • 避免上面尷尬問題的一個方法是 HTML5 Histroy 模式,感興趣的同窗能夠關注並實踐一下
  • 瞭解原理,瞭解設計模式,能夠借鑑到平時開發項目中

參考文檔