上一篇咱們寫了前端路由,這幾天比較閒,抽空研究了vue.js官方路由的vue-router的實現。html
本文主要是以vue-router2.7.0(https://github.com/vuejs/vue-router)版本的源代碼進行分析。前端
首先咱們來看一下目錄結構vue
這裏,先大概說明一下各個文件的做用,下面會詳細講解html5
components下是兩個組件<router-view> and <router-link> 的實現node
history是路由方式的封裝git
util下主要是各類功能類和功能函數github
create-matcher和create-router-map的做用是生成匹配表vue-router
index是整個插件的入口數組
Install 提供安裝的方法瀏覽器
看源代碼以前,咱們看一下vue-router的使用方法
import Vue from 'vue' import VueRouter from 'vue-router' //註冊插件 Vue.use(VueRouter) // 1. 定義(路由)組件。 // 能夠從其餘文件 import 進來 const Foo = { template: '<div>foo</div>' } const Bar = { template: '<div>bar</div>' } // 2. 定義路由 // 每一個路由應該映射一個組件。 其中"component" 能夠是 const routes = [ { path: '/foo', component: Foo }, { path: '/bar', component: Bar } ] // 3. 建立 router 實例,而後傳 `routes` 配置 const router = new VueRouter({ routes }) // 4. 建立和掛載根實例。 // 記得要經過 router 配置參數注入路由, // 從而讓整個應用都有路由功能 // 使用 router-link 組件來導航. // 路由出口 // 路由匹配到的組件將渲染在這裏 const app = new Vue({ router, template: ` <div id="app"> <h1>Basic</h1> <ul> <li><router-link to="/">/</router-link></li> <li><router-link to="/foo">/foo</router-link></li> <li><router-link to="/bar">/bar</router-link></li> <router-link tag="li" to="/bar">/bar</router-link> </ul> <router-view class="view"></router-view> </div> ` }).$mount('#app')
插件安裝
利用 Vue.js 提供的插件機制 .use(plugin) 來安裝 VueRouter,而這個插件機制則會調用該 plugin 對象的 install 方法
這裏咱們先分析註冊vueRouter類的兩個文件 src/index.js 和src/install.js
Index文件主要暴露了一個vueRouter類
xport default class VueRouter { constructor (options: RouterOptions = {}) { ..... } //初始化函數 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 if (history instanceof HTML5History) { 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 }) }) } } VueRouter.install = install VueRouter.version = '__VERSION__' //在外部直接引用vue-router,自動使用插件 if (inBrowser && window.Vue) { window.Vue.use(VueRouter) }
這裏是vue插件的經典寫法,給插件對象增長 install 方法用來安裝插件具體邏輯,同時在最後判斷下若是是在瀏覽器環境且存在 window.Vue 的話就會自動使用插件。
接下來,咱們從install看vue如何安裝插件
import View from './components/view' import Link from './components/link' export let _Vue export function install(Vue) { if (install.installed) return install.installed = true //私有化vue,方便引入 _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) } } Vue.mixin({ beforeCreate() { //首次進入初始化路由 if (isDef(this.$options.router)) { //根組件指向本身 this._routerRoot = this this._router = this.$options.router //初次進入對頁面進行路由 this._router.init(this) //監控 router數據變化,這裏爲更新router-view Vue.util.defineReactive(this, '_route', this._router.history.current) } else { //爲每一個組件傳遞根組件,方便訪問router信息 this._routerRoot = (this.$parent && this.$parent._routerRoot) || this } registerInstance(this, this) }, destroyed() { registerInstance(this) } }) /**router訪問的是根組件的router對象,就是傳入的router**/ Object.defineProperty(Vue.prototype, '$router', { get() { return this._routerRoot._router } }) /**route訪問的是根組件的router對象,就是傳入的route**/ Object.defineProperty(Vue.prototype, '$route', { get() { return this._routerRoot._route } }) //註冊router-view和router-link組件 Vue.component('router-view', View) Vue.component('router-link', Link) const strats = Vue.config.optionMergeStrategies // use the same hook merging strategy for route hooks strats.beforeRouteEnter = strats.beforeRouteLeave = strats.beforeRouteUpdate = strats.created }
install作了如下操做
一、對全部vue實例混入beforeCreate操做,設置_routerRoot向上傳遞指向根組件,根組件設置router對象
二、根組件首次進入的時候,初始化路由,將router對象掛載到根組件元素_router上,而且設置劫持數據_route
三、經過給 Vue.prototype 定義 $router、$route 屬性把他們注入到全部組件中(主要是爲了方便訪問router,route)
四、註冊兩個組件outer-view和router-link
建立 router 實例
在使用的實例中,咱們看到安裝完插件後,會實例一個router對象,把路由配置的數組做爲參數傳入,而且將其傳入vue實例的options中。接下來咱們看VueRouter類的做用。VueRouter在index.js文件中
export default class VueRouter { static install: () => void; static version: string; app: any; apps: Array<any>; ready: boolean; readyCbs: Array<Function>; options: RouterOptions; mode: string; history: HashHistory | HTML5History | AbstractHistory; matcher: Matcher; fallback: boolean; beforeHooks: Array<?NavigationGuard>; resolveHooks: Array<?NavigationGuard>; afterHooks: Array<?AfterNavigationHook>; constructor (options: RouterOptions = {}) { this.app = null this.apps = [] this.options = options this.beforeHooks = [] this.resolveHooks = [] this.afterHooks = [] this.matcher = createMatcher(options.routes || [], this) //生成匹配表 /*路由模式***/ let mode = options.mode || 'hash' /**兼容低版本不支持history模式*/ this.fallback = mode === 'history' && !supportsPushState && options.fallback !== false if (this.fallback) { mode = 'hash' } /**非瀏覽器***/ if (!inBrowser) { mode = 'abstract' } this.mode = mode /**門面模式封裝history***/ 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}`) } } }
這裏能夠看到構造函數中有一步操做
this.matcher = createMatcher(options.routes || [], this)
將傳入的routes配置數組處理爲 mather屬性的值,順藤摸瓜,咱們去createMatcher建立的文件src/create-matcher.js 文件中看他到底作了什麼操做。
export function createMatcher ( routes: Array<RouteConfig>, router: VueRouter ): Matcher { const { pathList, pathMap, nameMap } = createRouteMap(routes) //生成routermap表 function addRoutes (routes) { createRouteMap(routes, pathList, pathMap, nameMap) } function match ( raw: RawLocation, currentRoute?: Route, redirectedFrom?: Location ): Route { const location = normalizeLocation(raw, currentRoute, false, router) const { name } = location if (name) { const record = nameMap[name] if (process.env.NODE_ENV !== 'production') { warn(record, `Route with name '${name}' does not exist`) } if (!record) return _createRoute(null, location) const paramNames = record.regex.keys .filter(key => !key.optional) .map(key => key.name) if (typeof location.params !== 'object') { location.params = {} } if (currentRoute && typeof currentRoute.params === 'object') { for (const key in currentRoute.params) { if (!(key in location.params) && paramNames.indexOf(key) > -1) { location.params[key] = currentRoute.params[key] } } } if (record) { location.path = fillParams(record.path, location.params, `named route "${name}"`) return _createRoute(record, location, redirectedFrom) } } else if (location.path) { location.params = {} for (let i = 0; i < pathList.length; i++) { const path = pathList[i] const record = pathMap[path] if (matchRoute(record.regex, location.path, location.params)) { return _createRoute(record, location, redirectedFrom) } } } // no match return _createRoute(null, location) } function redirect ( record: RouteRecord, location: Location ): Route { //***** } function alias ( record: RouteRecord, location: Location, matchAs: string ): Route { //********* } function _createRoute ( record: ?RouteRecord, location: Location, redirectedFrom?: Location ): Route { //************ } return { match, addRoutes } }
在代碼中,咱們看到了createMatcher再一次將routes配置數組傳給了createRouteMap進一步處理
根據代碼分析createMatcher就是根據傳入的routes生成路由map對應表,而且返回match函數以及一個能夠增長路由配置項addRoutes函數,向上傳遞給VueRouter類暴露的接口addRoutes。
咱們繼續來看src/create-route-map.js下的createRouteMap如何生成map表的
export function createRouteMap ( routes: Array<RouteConfig>, oldPathList?: Array<string>, oldPathMap?: Dictionary<RouteRecord>, oldNameMap?: Dictionary<RouteRecord> ): { pathList: Array<string>; pathMap: Dictionary<RouteRecord>; nameMap: Dictionary<RouteRecord>; } { // the path list is used to control path matching priority const pathList: Array<string> = oldPathList || [] //路徑列表 const pathMap: Dictionary<RouteRecord> = oldPathMap || Object.create(null) //path路由map const nameMap: Dictionary<RouteRecord> = oldNameMap || Object.create(null) //名字路由map routes.forEach(route => { addRouteRecord(pathList, pathMap, nameMap, route) }) //確保通配符老是在最後 // ensure wildcard routes are always at the end for (let i = 0, l = pathList.length; i < l; i++) { if (pathList[i] === '*') { pathList.push(pathList.splice(i, 1)[0]) l-- i-- } } return { pathList, pathMap, nameMap } } function addRouteRecord ( pathList: Array<string>, pathMap: Dictionary<RouteRecord>, nameMap: Dictionary<RouteRecord>, route: RouteConfig, parent?: RouteRecord, matchAs?: string ) { const { path, name } = route if (process.env.NODE_ENV !== 'production') { assert(path != null, `"path" is required in a route configuration.`) assert( typeof route.component !== 'string', `route config "component" for path: ${String(path || name)} cannot be a ` + `string id. Use an actual component instead.` ) } const pathToRegexpOptions: PathToRegexpOptions = route.pathToRegexpOptions || {} const normalizedPath = normalizePath( path, parent, pathToRegexpOptions.strict ) if (typeof route.caseSensitive === 'boolean') { pathToRegexpOptions.sensitive = route.caseSensitive } //封裝route記錄 const record: RouteRecord = { path: normalizedPath, //路徑 regex: compileRouteRegex(normalizedPath, pathToRegexpOptions), //轉化爲匹配數組 components: route.components || { default: route.component }, //關聯組件 instances: {}, //實例 name, //名字 parent, //父級router matchAs, redirect: route.redirect, //跳轉 beforeEnter: route.beforeEnter, //進入前操做 meta: route.meta || {}, //附加參數 props: route.props == null //props屬性 ? {} : route.components ? route.props : { default: route.props } } //子路由 if (route.children) { //子路由收集 route.children.forEach(child => { const childMatchAs = matchAs ? cleanPath(`${matchAs}/${child.path}`) : undefined addRouteRecord(pathList, pathMap, nameMap, child, record, childMatchAs) }) } //別名 if (route.alias !== undefined) { const aliases = Array.isArray(route.alias) ? route.alias : [route.alias] aliases.forEach(alias => { const aliasRoute = { path: alias, children: route.children } addRouteRecord( pathList, pathMap, nameMap, aliasRoute, parent, record.path || '/' // matchAs ) }) } //存儲。按路徑存儲 if (!pathMap[record.path]) { pathList.push(record.path) pathMap[record.path] = record } //按名字存儲 if (name) { if (!nameMap[name]) { nameMap[name] = record } else if (process.env.NODE_ENV !== 'production' && !matchAs) { warn( false, `Duplicate named routes definition: ` + `{ name: "${name}", path: "${record.path}" }` ) } } }
這裏能夠看到遍歷routes根據path和名name將每個路由項處理爲一個routerRecord,而且分類保存到pathMap 和 nameMap,方便後續的匹配操做。
接下來,咱們繼續往下看vueRouter的構造函數。根據不一樣的模式生成history對象,那history對象究竟是什麼呢?咱們接下去看History類。全部的History類都繼承自基類base.js
export class History { router: Router; //router對象 base: string; //基準路徑 current: Route; //當前的路由 pending: ?Route; cb: (r: Route) => void; //回調 ready: boolean; readyCbs: Array<Function>; readyErrorCbs: Array<Function>; errorCbs: Array<Function>; // 子類實現 +go: (n: number) => void; +push: (loc: RawLocation) => void; +replace: (loc: RawLocation) => void; +ensureURL: (push?: boolean) => void; +getCurrentLocation: () => string; constructor (router: Router, base: ?string) { this.router = router this.base = normalizeBase(base) //返回基準路徑 this.current = START //route 設置當前route this.pending = null this.ready = false this.readyCbs = [] this.readyErrorCbs = [] this.errorCbs = [] } listen (cb: Function) { this.cb = cb } onReady (cb: Function, errorCb: ?Function) { //***** } onError (errorCb: Function) { //***** } //路由轉化操做 transitionTo (location: RawLocation, onComplete?: Function, onAbort?: Function) { const route = this.router.match(location, this.current) //找到匹配路由 this.confirmTransition(route, () => { //確認是否轉化 this.updateRoute(route) //更新route onComplete && onComplete(route) this.ensureURL() // fire ready cbs once if (!this.ready) { this.ready = true this.readyCbs.forEach(cb => { cb(route) }) } }, err => { if (onAbort) { onAbort(err) } if (err && !this.ready) { this.ready = true this.readyErrorCbs.forEach(cb => { cb(err) }) } }) } //確認是否轉化路由 confirmTransition (route: Route, onComplete: Function, onAbort?: Function) { const current = this.current const abort = err => { if (isError(err)) { if (this.errorCbs.length) { this.errorCbs.forEach(cb => { cb(err) }) } else { warn(false, 'uncaught error during route navigation:') console.error(err) } } onAbort && onAbort(err) } //判斷若是先後是同一個路由,不進行操做 if ( isSameRoute(route, current) && route.matched.length === current.matched.length ) { this.ensureURL() return abort() } //下面是各種鉤子函數的處理 //********************* }) } //更新路由 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) }) } }
history/base.js實現了基本history的操做,history/hash.js,history/html5.js和history/abstract.js繼承了base,只是根據不一樣的模式封裝了一下幾個函數的基本操做
+go: (n: number) => void; +push: (loc: RawLocation) => void; +replace: (loc: RawLocation) => void; +ensureURL: (push?: boolean) => void; +getCurrentLocation: () => string;
能夠看到有幾個重要的操做函數,transitionTo對於路由更新的控制以及更新路由,updateRoute調用了咱們在vue-router中註冊的函數
history.listen(route => { this.apps.forEach((app) => { app._route = route }) })
這一步很重要,更新_route的值,還記得咱們在install中作的操做
Vue.util.defineReactive(this, '_route', this._router.history.current)
劫持了_route對象,所裏這裏路由更新_route,致使了視圖更新。
接下來,咱們來看如何調用transitionTo,達到更新目的,transitionTo的調用都是在代碼幾個子類的實現裏面。
//hash.js //設置路由,監控路由改變 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() } setupListeners () { window.addEventListener('hashchange', () => { if (!ensureSlash()) { return } this.transitionTo(getHash(), route => { replaceHash(route.fullPath) }) }) } //push方法 push (location: RawLocation, onComplete?: Function, onAbort?: Function) { this.transitionTo(location, route => { pushHash(route.fullPath) onComplete && onComplete(route) }, onAbort) } //replace方法 replace (location: RawLocation, onComplete?: Function, onAbort?: Function) { this.transitionTo(location, route => { replaceHash(route.fullPath) onComplete && onComplete(route) }, onAbort) } } //html5.js實現 export class HTML5History extends History { constructor (router: Router, base: ?string) { super(router, base) const expectScroll = router.options.scrollBehavior //指回滾方式 if (expectScroll) { setupScroll() } //監控popstate事件 window.addEventListener('popstate', e => { const current = this.current this.transitionTo(getLocation(this.base), route => { if (expectScroll) { handleScroll(router, route, current, true) } }) }) } //push push (location: RawLocation, onComplete?: Function, onAbort?: Function) { const { current: fromRoute } = this this.transitionTo(location, route => { pushState(cleanPath(this.base + 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 => { replaceState(cleanPath(this.base + route.fullPath)) //保存當前的位置信息,用於返回時候復位 handleScroll(this.router, route, fromRoute, false) onComplete && onComplete(route) }, onAbort) } } //abstract.js實現,這裏經過棧的數據結構來模擬路由路徑 export class AbstractHistory extends History { index: number; stack: Array<Route>; constructor (router: Router, base: ?string) { super(router, base) this.stack = [] this.index = -1 } push (location: RawLocation, onComplete?: Function, onAbort?: Function) { this.transitionTo(location, route => { this.stack = this.stack.slice(0, this.index + 1).concat(route) this.index++ onComplete && onComplete(route) }, onAbort) } replace (location: RawLocation, onComplete?: Function, onAbort?: Function) { this.transitionTo(location, route => { this.stack = this.stack.slice(0, this.index).concat(route) onComplete && onComplete(route) }, onAbort) } }
這裏,3種方式都提供了replace和push接口來更新路由同時hash模式監控 hashchange,H5模式監控 popstate
這裏H5模式多了一步保存當前的位置信息,用於返回時候復位的操做
除了在子類調用以外,在 vueRouter類中init也有調用
if (history instanceof HTML5History) { history.transitionTo(history.getCurrentLocation()) } else if (history instanceof HashHistory) { //創建hash監控 const setupHashListener = () => { history.setupListeners() } history.transitionTo( history.getCurrentLocation(), setupHashListener, setupHashListener ) } }
由於在這兩種模式下才有可能存在進入時候的不是默認頁,須要根據當前瀏覽器地址欄裏的 path 或者 hash 來激活對應的路由,此時就是經過調用 transitionTo 來達到目的
接着繼續追蹤replace和push的調用,這兩個方法的觸發經過咱們定義的router-link組件
render (h: Function) { const router = this.$router //路由對象 const current = this.$route //當前路由 //解析 to的路徑對應路由項 const { location, route, href } = router.resolve(this.to, current, this.append) //設置一些默認元素class const classes = {} const globalActiveClass = router.options.linkActiveClass const globalExactActiveClass = router.options.linkExactActiveClass // Support global empty active class const activeClassFallback = globalActiveClass == null ? 'router-link-active' : globalActiveClass const exactActiveClassFallback = globalExactActiveClass == null ? 'router-link-exact-active' : globalExactActiveClass const activeClass = this.activeClass == null ? activeClassFallback : this.activeClass const exactActiveClass = this.exactActiveClass == null ? exactActiveClassFallback : this.exactActiveClass /**根據當前路由設置當前對象**/ const compareTarget = location.path ? createRoute(null, location, null, router) : route // 若是嚴格模式的話 就判斷是不是相同路由(path query params hash) // 不然就走包含邏輯(path包含,query包含 hash爲空或者相同) classes[exactActiveClass] = isSameRoute(current, compareTarget) classes[activeClass] = this.exact ? classes[exactActiveClass] : isIncludedRoute(current, compareTarget) //事件處理函數 const handler = e => { if (guardEvent(e)) { if (this.replace) {//路由replace觸發改變router-view router.replace(location) } else {//路由push觸發改變router-view router.push(location) } } } //事件對象 const on = { click: guardEvent } if (Array.isArray(this.event)) { this.event.forEach(e => { on[e] = handler }) } else { on[this.event] = handler } //添加元素的類 const data: any = { class: classes } if (this.tag === 'a') { data.on = on data.attrs = { href } } else { // find the first <a> child and apply listener and href const a = findAnchor(this.$slots.default) if (a) { // in case the <a> is a static node a.isStatic = false const extend = _Vue.util.extend const aData = a.data = extend({}, a.data) aData.on = on const aAttrs = a.data.attrs = extend({}, a.data.attrs) aAttrs.href = href } else { // doesn't have <a> child, apply listener to self data.on = on } } return h(this.tag, data, this.$slots.default) } } function guardEvent (e) { // 忽略帶有功能鍵的點擊 if (e.metaKey || e.altKey || e.ctrlKey || e.shiftKey) return // 已阻止的返回 if (e.defaultPrevented) return // 右擊 if (e.button !== undefined && e.button !== 0) return // `target="_blank"` 忽略 if (e.currentTarget && e.currentTarget.getAttribute) { const target = e.currentTarget.getAttribute('target') if (/\b_blank\b/i.test(target)) return } // 阻止默認行爲 防止跳轉 if (e.preventDefault) { e.preventDefault() } return true } //找到第一個A標籤 function findAnchor (children) { if (children) { let child for (let i = 0; i < children.length; i++) { child = children[i] if (child.tag === 'a') { return child } if (child.children && (child = findAnchor(child.children))) { return child } } } }
能夠看到router-link綁定了click 方法調用replace和push 達到更新路由目的。
最後,咱們來看router-view如何是如何更新的
export default { name: 'router-view', functional: true, // 功能組件 純粹渲染 props: { name: { type: String, default: 'default' } }, render (_, { props, children, parent, data }) { //標記爲routerview data.routerView = true // directly use parent context's createElement() function //直接使用父組件上下文的createElement()函數 // so that components rendered by router-view can resolve named slots const h = parent.$createElement const name = props.name const route = parent.$route const cache = parent._routerViewCache || (parent._routerViewCache = {}) //緩存 // determine current view depth, also check to see if the tree // has been toggled inactive but kept-alive. let depth = 0 let inactive = false //解決router-view 嵌套問題 while (parent && parent._routerRoot !== parent) { if (parent.$vnode && parent.$vnode.data.routerView) { depth++ } //是不是keep-alive if (parent._inactive) { inactive = true } parent = parent.$parent } //當前view-router的嵌套深度 data.routerViewDepth = depth // render previous view if the tree is inactive and kept-alive if (inactive) { return h(cache[name], data, children) } // 獲得相匹配的當前組件層級的 路由記錄 const matched = route.matched[depth] // render empty node if no matched route if (!matched) { cache[name] = null return h() } //緩存組件 const component = cache[name] = matched.components[name] // attach instance registration hook // this will be called in the instance's injected lifecycle hooks data.registerRouteInstance = (vm, val) => { // val could be undefined for unregistration const current = matched.instances[name] if ( (val && current !== vm) || (!val && current === vm) ) { matched.instances[name] = val } } // also register instance in prepatch hook // in case the same component instance is reused across different routes ;(data.hook || (data.hook = {})).prepatch = (_, vnode) => { matched.instances[name] = vnode.componentInstance } // resolve props data.props = resolveProps(route, matched.props && matched.props[name]) return h(component, data, children) } }
能夠看到邏輯仍是比較簡單的,根據route拿到匹配的組件進行渲染就能夠了。裏面比較複雜的是對於組件的緩存處理。
這裏,整個流程就徹底走完了。可能還有些懵,咱們下面就在總結一下整個流程。
一、安裝插件
完成了router-link和 router-view 兩個組件的註冊,router-link用於觸發路由的變化,router-view做爲功能組件,用於觸發對應路由視圖的變化
混入beforeCreate生命週期處理,初始化_routerRoot,_router,_route等數據
全局設置VUE靜態訪問$router和$route,方便後期訪問
二、根據路由配置生成router實例
根據配置數組生成路由配置記錄表
生成監控路由變化的hsitory對象
三、將router實例傳入根VUE實例
根據beforeCreate混入,爲根vue對象設置了劫持字段_route,用戶觸發router-view的變化
調用init()函數,完成首次路由的渲染,首次渲染的調用路徑是 調用history.transitionTo方法,根據router的match函數,生成一個新的route對象,接着經過confirmTransition對比一下新生成的route和當前的route對象是否改變,改變 的話觸發updateRoute,更新hsitory.current屬性,觸發根組件的_route的變化,從而致使組件的調用render函數,更新router-view。
另一種更新路由的方式是主動觸發,router-link綁定了click方法,觸發history.push或者history.replace,從而觸發history.transitionTo
同時會監控hashchange和popstate來對路由變化做對用的處理