# vue-router原理
# 前言
vue作为最受欢迎的前端框架之一,vue-router又是官方推出的路由管理库SPA思想- 面试
 - 源码预览 (opens new window)
 - 本文基于
vue-router@3.x版本 
# 应用配置
一般会这么配置
shell安装
npm install (or yarn add) vue-router
router.js
import VueRouter from 'vue-router'
import Vue from 'vue'
Vue.use(VueRouter)
const router = new VueRouter({
    routes: ....,
    base,
    mode: 'hash | history',
    ...others
})
export default router;
main.js
import router from 'router.js';
new Vue({
    router,
    ...
})
 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
页面使用路由跳转
# 思考几个点
上面这几段代码值得思考的有几个点
- router.js中单独引入vue,并通过vue.use()挂载到全局,和main.js中引入的vue,会是同一个实例吗?
 - vue.use()这中间会涉及到什么
 - vue-router暴露了router-link、router-view这两个全局组件
 - 另外vue原型挂载了$route、$router这个实例、并附带了一些方法。如push、replace等等
 - 另外vue-router有三种mode模式。history、hash、abstract这中间到底干了什么
 下面带着这些点逐步探究
# 不同文件间引入vue、这中间有影响吗?(main.js中引入vue、和router.js中都从vue包中引入,最后实例this会有冲突吗?)
不会
可以这么理解,单文件作用域都是隔离的,也就是说这中间vue,只会引入一次,符合tree-shaking,最后都会合并同一个实例上面
# vue.use()执行机制
vue.use注册一个组件,会调用这个组件实例上的install方法、并将vue以形参传入install方法
# 首先挂载vue-router
import { install } from './install';
import { createMatcher } from './create-matcher';
import { HashHistory } from './history/hash';
import { HTML5History } from './history/html5';
import { AbstractHistory } from './history/abstract';
const inBrowser = typeof window !== 'undefined';
export default class VueRouter {
  constructor(options) {
    // 路由配置
    this.options = options;
    // 创建路由matcher对象,传入routes路由配置列表及VueRouter实例,主要负责url匹配
    this.matcher = createMatcher(options.routes);
    let mode = options.mode || 'hash';
    // 支持所有 JavaScript 运行环境,非浏览器环境强制使用abstract模式,主要用于SSR
    if (!inBrowser) {
      mode = 'abstract';
    }
    this.mode = mode;
    // 根据不同mode,实例化不同history实例
    switch (mode) {
      case 'history':
        console.log('history');
        this.history = new HTML5History(this);
        break;
      case 'hash':
        console.log('hash');
        this.history = new HashHistory(this);
        break;
      case 'abstract':
        console.log('abstract');
        this.history = new AbstractHistory(this);
        break;
      default:
        if (process.env.NODE_ENV !== 'production') {
          throw new Error(`[vue-router] invalid mode: ${mode}`);
        }
    }
  }
  init(app) {
    // 绑定destroyed hook,避免内存泄露
    app.$once('hook:destroyed', () => {
      this.app = null;
      if (!this.app) this.history.teardown();
    });
    // 存在即不需要重复监听路由
    if (this.app) return;
    this.app = app;
    // 跳转当前路由path匹配渲染 用于页面初始化
    this.history.transitionTo(
      // 获取当前页面 path
      this.history.getCurrentLocation(),
      () => {
        // 启动监听
        this.history.setupListeners();
      }
    );
    // 传入赋值回调,为_route赋值,进而触发router-view的重新渲染,当前路由对象改变时调用
    this.history.listen((route) => {
      app._route = route;
    });
  }
  // 匹配路由
  match(location) {
    return this.matcher.match(location);
  }
  // 获取所有活跃的路由记录列表
  getRoutes() {
    return this.matcher.getRoutes();
  }
  // 动态添加路由(添加一条新路由规则)
  addRoute(parentOrRoute, route) {
    this.matcher.addRoute(parentOrRoute, route);
  }
  // 动态添加路由(参数必须是一个符合 routes 选项要求的数组)
  addRoutes(routes) {
    this.matcher.addRoutes(routes);
  }
  // 导航到新url,向 history栈添加一条新访问记录
  push(location) {
    this.history.push(location);
  }
  // 在 history 记录中向前或者后退多少步
  go(n) {
    this.history.go(n);
  }
  // 导航到新url,替换 history 栈中当前记录
  replace(location, onComplete) {
    this.history.replace(location, onComplete);
  }
  // 导航回退一步
  back() {
    this.history.go(-1);
  }
}
VueRouter.install = install;
 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
install.js部分代码
Vue.mixin({
    // Vue创建前钩子,此生命周期$options已挂载完成
    beforeCreate() {
      // 通过判断组件实例this.$options有无router属性来判断是否为根实例
      // 只有根实例初始化时我们挂载了VueRouter实例router(main.js中New Vue({router})时)
      if (this.$options.router) {
        this._routerRoot = this;
        // 在 Vue 根实例添加 _router 属性( VueRouter 实例)
        this._router = this.$options.router;
        // 调用VueRouter实例初始化方法
        // _router即VueRouter实,此处this即Vue根实例
        this._router.init(this);
        // 把 ($route <=> _route) 处理为响应式的
        Vue.util.defineReactive(this, '_route', this._router.history.current);
      } else {
        // 为每个组件实例定义_routerRoot,回溯查找_routerRoot
        this._routerRoot = (this.$parent && this.$parent._routerRoot) || this;
      }
    }
  });
 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
vue-router将实例以递归的形式挂载到根实例上,这样就避免了创建了多个实例对象,减少性能消耗- 调用
 Vue.util.defineReactive响应式代理this._route达到监听页面变化this._router.init(this);其实调用的是router原型上init方法
# 不同路由模式监听机制
- 不同与传统页面渲染机制,vuejs以单页面应用的形式渲染(single page application)
 - 单页面渲染的优势,首页资源加载比较多,但是用户跳转路由页面是不刷新的,这主要归功于html5提供的hash模式和history模式
 - 通过监听hash或者history变化响应页面更新
 
base.js 
hash.js 
html5.js 
暴露的几个常用的api
# router-link、router-view
 以全局组件形式挂载
router-link
# $router、 $route
 # 最后
vue-router源码很多,源码阅读、手写源码等只是你学习这其中的思想,更多的是思考为什么要这么写 欢迎大家留言交流