容易忽略的URL

keelvin發表於2019-03-03

場景再現

眾所周知,vue-router有三種模式 :hashhtml5abstract , 一般的前端專案中會選擇hash模式進行開發,最近做了一個運營活動就是基於vue-router的hash模式進行開發的。

  • 專案註冊了兩個路由(抽象出來的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=123

router.push({ 
    path: `demo`,
    query: { 
        plan: `private`
    }
})
複製程式碼

產生質疑

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

質疑探究

URL標準

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

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

例子

下圖展示了兩個 URI 例子及它們的組成部分。

                    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中的『?』『#』

  • 『?』

    • 路徑與引數分隔符
    • 瀏覽器只識別url中的第一個『?』,後面的會當做引數處理
  • 『#』

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

URL讀取和操作

URL讀取和操作涉及location和history兩個物件,具體如下:

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 模式,感興趣的同學可以關注並實踐一下
  • 瞭解原理,瞭解設計模式,可以借鑑到平時開發專案中

參考文件

相關文章