text: /* @flow */
  
  import type Router from '../index'
  import { History } from './base'
  import { cleanPath } from '../util/path'
  import { getLocation } from './html5'
  import { setupScroll, handleScroll } from '../util/scroll'
  import { pushState, replaceState, supportsPushState } from '../util/push-state'
  
  // 有个父类 History
  export class HashHistory extends History {
    constructor (router: Router, base: ?string, fallback: boolean) {
      super(router, base)
      // check history fallback deeplinking
      if (fallback && checkFallback(this.base)) {
        return
      }
      // 确保斜线
      ensureSlash()
    }
  
    // this is delayed until the app mounts to avoid the hashchange listener being fired too early
    // 这会延迟到应用程序挂载，以避免hashchange侦听器触发得太早
    setupListeners () {
      if (this.listeners.length > 0) {
        return
      }
  
      const router = this.router
      const expectScroll = router.options.scrollBehavior
      const supportsScroll = supportsPushState && expectScroll
  
      if (supportsScroll) {
        this.listeners.push(setupScroll())
      }
  
      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)
      })
    }
  
    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
      )
    }
  
    go (n: number) {
      window.history.go(n)
    }
  
    ensureURL (push?: boolean) {
      const current = this.current.fullPath
      if (getHash() !== current) {
        push ? pushHash(current) : replaceHash(current)
      }
    }
  
    getCurrentLocation () {
      return getHash()
    }
  }
  
  function checkFallback (base) {
    const location = getLocation(base)
    if (!/^\/#/.test(location)) {
      window.location.replace(cleanPath(base + '/#' + location))
      return true
    }
  }
  
  // 确保 #之后的路径 有斜线, 否则给#号后的加上'/'
  function ensureSlash (): boolean {
    // 拿到#之后的路径
    // 比如  http://localhost:8081/basic/#/aa, 拿到'/a'
    const path = getHash()
    if (path.charAt(0) === '/') {
      return true
    }
    replaceHash('/' + path)
    return false
  }
  
  // 拿到#之后的路径 (并解好码)
  // 比如  http://localhost:8081/basic/#/aa, 拿到'/a'
  export function getHash (): string {
    // We can't use window.location.hash here because it's not
    // consistent across browsers - Firefox will pre-decode it!
    // 我们不能在此处使用window.location.hash，因为它不是跨浏览器保持一致的Firefox将对其进行预解码！
    let href = window.location.href
    const index = href.indexOf('#')
    // empty path
    if (index < 0) return ''
  
    href = href.slice(index + 1)
    // decode the hash but not the search or hash
    // as search(query) is already decoded
    // https://github.com/vuejs/vue-router/issues/2708
    const searchIndex = href.indexOf('?')
    if (searchIndex < 0) {
      const hashIndex = href.indexOf('#')
      if (hashIndex > -1) {
        href = decodeURI(href.slice(0, hashIndex)) + href.slice(hashIndex)
      } else href = decodeURI(href)
    } else {
      href = decodeURI(href.slice(0, searchIndex)) + href.slice(searchIndex)
    }
  
    return href
  }
  
  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
    }
  }
  
  function replaceHash (path) {
    if (supportsPushState) {
      replaceState(getUrl(path))
    } else {
      window.location.replace(getUrl(path))
    }
  }
  