1. 程式人生 > >容易忽略的URL

容易忽略的URL

場景再現

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

參考文件