Vue进阶(七十七):vue 路由的两种模式:hash与history_vue创建中的history

28 阅读3分钟
    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}`)
    }
}

} // 初始化操作, app表示组件实例 init (app: any /* Vue component instance */) { // ... this.apps.push(app)

// main app already initialized.
if (this.app) {
  return
}

this.app = app
const history = this.history

// 根据history的类别执行相应的初始化操作和监听
if (history instanceof HTML5History) {
  // html5模式
  history.transitionTo(history.getCurrentLocation())
} else if (history instanceof HashHistory) {
  // hash模式 
  const setupHashListener = () => {
    history.setupListeners()
  }
  history.transitionTo(
    history.getCurrentLocation(),
    setupHashListener,
    setupHashListener
  )
}

// 切换路由
history.listen(route => {
  this.apps.forEach((app) => {
    app._route = route
  })
})

} // 一些钩子函数 beforeEach、 afterEach等 // ... // ...

// VueRouter类暴露的以下方法实际是调用具体history对象的方法 push (location: RawLocation, onComplete?: Function, onAbort?: Function) { this.history.push(location, onComplete, onAbort) }

replace (location: RawLocation, onComplete?: Function, onAbort?: Function) { this.history.replace(location, onComplete, onAbort) } } // 相对于当前页面向前或向后跳转多少个页面,类似 window.history.go(n)。n可为正数可为负数。正数返回上一个页面 go (n: number) { this.history.go(n) } // 后退到上一个页面 back () { this.go(-1) } // 前进到下一个页面 forward () { this.go(1) }

// ... // ...


代码解读:


* 浏览器默认是`hash`模式,当浏览器不支持html5的`history`模式时,也会强制为`hash`模式;当环境不是浏览器时,强制为`abstract`模式。
* 当创建`VueRouter`实例后,`VueRouter`构造函数会通过传入对象`options``mode`参数,来调用对应的(`HashHistory / HTML5History / AbstractHistory`)构造函数,进而创建对应的`history`实例对象。
* 创建相应的`history`实例后,可以看到`init`函数里面会有对应的初始化操作。


`$router`实例有两个常见的跳转方法:`push``replace`方法,源码的最下面,就暴露出来这两个方法。很显然这两种方法都是对不同模式下的方法的封装,本质还是执行的对应模式上的方法。


`HashHistory`中的`push()`方法:



push (location: RawLocation, onComplete?: Function, onAbort?: Function) { // 执行transitionTo函数 this.transitionTo(location, route => { // 改变hash值 pushHash(route.fullPath) onComplete && onComplete(route) }, onAbort) }

function pushHash (path) { window.location.hash = path }


`pushHash`直接对`window.location.hash`赋值,`hash`值变化之后,浏览器访问历史中就会增加一个记录。


记录增加了,如何更新视图呢?接着看父类History中`transitionTo`函数是如何实现的。


`transtitionTo`函数具体实现如下:



transitionTo (location: RawLocation, onComplete?: Function, onAbort?: Function) { //找到匹配路由(match函数) const route = this.router.match(location, this.current) this.confirmTransition(route, () => { //确认是否转化 this.updateRoute(route) //更新route // ... }) }

//更新路由 updateRoute (route: Route) { const prev = this.current // 跳转前路由 this.current = route // 准备跳转路由 this.cb && this.cb(route) // 回调函数,这一步很重要,这个回调函数在index文件中注册,会更新被劫持的数据 _router this.router.afterHooks.forEach(hook => { hook && hook(route, prev) }) } }

// this.cb函数的定义 listen (cb: Function) { this.cb = cb }

`transitionTo`函数中,执行了`updateRoute`更新路由函数,这个函数中执行了`cb`这个函数,`cb`这个函数是在`VueRouter`实例中的`init`函数中通过`history.listen`传入的。



init (app: any /* Vue component instance */) {
// ... this.apps.push(app) // ... history.listen(route => { this.apps.forEach((app) => { app._route = route }) }) }


上面的代码,从`$router.push` 分析到了`cb()`函数, 再接着看cb函数,这个函数中有一个`_route`属性,并且将匹配的路由`route`赋给了`app._route`。(这里app是组件实例,apps是所有组件实例一个数组) 。


那么`_route`属性是什么,组件本身是没有定义这个属性的,那么这个属性从哪里来的呢?源码中在`install()`方法中使用`Vue.mixin()`方法添加一个全局的混合对象:


`install.js`代码里面做了四件事:


1. 混入`beforeCreate`函数,在里面定义`_route`这个响应式属性。
2. 混入`destroyed`函数。
3.`Vue.prototype`上定义`$router``$route`这两个对象,以便每个组件都可以获得。
4. 在Vue`上`注册`router-link``router-view`这两个组件。



// install.js代码: export function install (Vue) { if (install.installed && _Vue === Vue) return install.installed = true

_Vue = Vue

const isDef = v => v !== undefined

const registerInstance = (vm, callVal) => { let i = vm.$options._parentVnode if (isDef(i) && isDef(i = i.data) && isDef(i = i.registerRouteInstance)) { i(vm, callVal) } }

// 使用mixin会在每个.vue文件中进行beforeCreate和destroyed Vue.mixin({ //对每个Vue实例混入beforeCreate钩子操作 //验证Vue实例是否有router对象了,如果有,就不再初始化了 beforeCreate () { // 如果没有router对象(this.option.router来自于VueRouter实例router对象)if(isDef(this.option.router来自于VueRouter实例router对象) if (isDef(this.options.router)) { // 将_routerRoot指向当前组件 this._routerRoot = this // 将router对象挂载到根组件的_router上 this._router = this.options.router // 调用VueRouter实例的init方法 this._router.init(this) // 劫持数据\_route,一旦\_route数据变化之后,通知router-view执行render方法 Vue.util.defineReactive(this, '\_route', this._router.history.current) } else { // 如果有router对象,则将每一个组件的\_routerRoot指向根Vue实例 this._routerRoot = (this.parent && this.parent._routerRoot) || this } // 注册vuecomponent实例 registerInstance(this, this) }, destroyed () { // 销毁vuecomponent实例 registerInstance(this) } }) //通过Vue.prototype定义router、route属性(方便所有组件可以获取这两个属性)Object.defineProperty(Vue.prototype,route 属性(方便所有组件可以获取这两个属性) Object.defineProperty(Vue.prototype, 'router', { get () { return this._routerRoot._router } })

Object.defineProperty(Vue.prototype, '$route', { get () { return this._routerRoot._route } }) //Vue上注册router-link和router-view两个组件 Vue.component('RouterView', View) Vue.component('RouterLink', Link)

const strats = Vue.config.optionMergeStrategies // use the same hook merging strategy for route hooks strats.beforeRouteEnter = strats.beforeRouteLeave = strats.beforeRouteUpdate = strats.created }

`beforeCreate`钩子中,通过`Vue.util.defineReactive()`创建一个响应式`_route`属性。`router-view`组件挂载时,会生成一个`watcher`,当`hash`值变化时,匹配到新的路由`route`后,`_route`属性跟着改变,触发`_route`属性的`getter`,进行依赖收集`watcher`,最后触发`_route``setter`,执行`watcher`的更新函数,进而触发`<router-view>``render`函数,更新视图。


大致流程为:



$router.push-> history.push -> transitionTo->updateRoute->cb(即 app._route = route) -> render


1. 触发`$router.push()`### 学习笔记

主要内容包括**html,css,html5,css3,JavaScript,正则表达式,函数,BOM,DOM,jQuery,AJAX,vue**等等

>**HTML/CSS**
>


**HTML:**HTML基本结构,标签属性,事件属性,文本标签,多媒体标签,列表 / 表格 / 表单标签,其他语义化标签,网页结构,模块划分

**CSS:**CSS代码语法,CSS 放置位置,CSS的继承,选择器的种类/优先级,背景样式,字体样式,文本属性,基本样式,样式重置,盒模型样式,浮动float,定位position,浏览器默认样式

![](https://p9-xtjj-sign.byteimg.com/tos-cn-i-73owjymdk6/63b0cc6ad30c4293bf82823105728432~tplv-73owjymdk6-jj-mark-v1:0:0:0:0:5o6Y6YeR5oqA5pyv56S-5Yy6IEAg55So5oi3NTc5MjMwMTY3MDI=:q75.awebp?rk3s=f64ab15b&x-expires=1771414605&x-signature=AmWpKZWwbWSOFpavuf%2F9adw3Ab4%3D)

>**HTML5 /CSS3**

**HTML5:**HTML5 的优势,HTML5 废弃元素,HTML5 新增元素,HTML5 表单相关元素和属性

**CSS3:**CSS3 新增选择器,CSS3 新增属性,新增变形动画属性,3D变形属性,CSS3 的过渡属性,CSS3 的动画属性,CSS3 新增多列属性,CSS3新增单位,弹性盒模型

![](https://p9-xtjj-sign.byteimg.com/tos-cn-i-73owjymdk6/93b0d515b59447309520e2193e5d8292~tplv-73owjymdk6-jj-mark-v1:0:0:0:0:5o6Y6YeR5oqA5pyv56S-5Yy6IEAg55So5oi3NTc5MjMwMTY3MDI=:q75.awebp?rk3s=f64ab15b&x-expires=1771414605&x-signature=JBJQhub3bXpJsVc%2Bc7PlY2Iou6c%3D)

>**JavaScript**

**JavaScript:**JavaScript基础,JavaScript数据类型,算术运算,强制转换,赋值运算,关系运算,逻辑运算,三元运算,分支循环,switch,while,do-while,for,break,continue,数组,数组方法,二维数组,字符串

![](https://p9-xtjj-sign.byteimg.com/tos-cn-i-73owjymdk6/cee83c290aa5409d9aa2ac25cf438335~tplv-73owjymdk6-jj-mark-v1:0:0:0:0:5o6Y6YeR5oqA5pyv56S-5Yy6IEAg55So5oi3NTc5MjMwMTY3MDI=:q75.awebp?rk3s=f64ab15b&x-expires=1771414605&x-signature=ivHEV5IWDAMEzqUO2vyNiO9f0nw%3D)
**开源分享:https://docs.qq.com/doc/DSmRnRGxvUkxTREhO**