/* @flow *///install: vue.use( VueRouter ) 时调用的 install() 方法。
import { install } from './install'
//START: 初始化 route 对象。
import { START } from './util/route'
//assert, warn: 断言和警告
import { assert, warn } from './util/warn'
//inBrowser: 判断是不是处于浏览器环境。
import { inBrowser } from './util/dom'
//cleanPath: 清除路径上连续的 “/”。
import { cleanPath } from './util/path'
//createMatcher:常见 matcher 对象。
import { createMatcher } from './create-matcher'
//normalizeLocation: 将 url,query,hash 进行归一化处理
import { normalizeLocation } from './util/location'
//supportsPushState: 判断是否支持 html5 history。
import { supportsPushState } from './util/push-state'
//handleScroll: 操作浏览器滚动
import { handleScroll } from './util/scroll'
//HashHistory: hash模式下的 history 历史记录操作对象。
import { HashHistory } from './history/hash'
//HTML5History: history模式下的 h5 history 历史记录操作对象。
import { HTML5History } from './history/html5'
//AbstractHistory: 非浏览器下的 hisotry 对象。
import { AbstractHistory } from './history/abstract'// Matcher 类
// 1、用于创建 router;
// 2、用于添加 route config(即用户配置的 routes 中的元素数据)
// 3、用于获取所有的 route config 转换为的 record 对象。
import type { Matcher } from './create-matcher'//用于识别是不是导航错误,以及具体错误类型,比如重定向错误。
import { isNavigationFailure, NavigationFailureType } from './util/errors'export default class VueRouter {//  new VueRouter( options ) 创建 VueRouter 对象时的初始化。constructor(options: RouterOptions = {}) {//非生产环境下,判断 this 是不是 VueRouter 实例。如果不是就报警告。if (process.env.NODE_ENV !== 'production') {warn(this instanceof VueRouter,`Router must be called with the new operator.`)}//用于记录第一个 vue 实例。也就是所有的 vue 实例的祖先。this.app = null//用于记录所有的 vue 实例。this.apps = []//new VueRouter( options ) 的 options 参数。this.options = options//用于记录路由跳转前的钩子函数。这里面的钩子函数能拦截和改变路由跳转。this.beforeHooks = []//以用 router.beforeResolve 注册一个全局守卫。这和 router.beforeEach 类似,因为它在每次导航时都会触发,//但是确保在导航被确认之前,同时在所有组件内守卫和异步路由组件被解析之后,解析守卫就被正确调用。this.resolveHooks = []//用于记录路由跳转后的钩子函数。不能拦截路由跳转,this.afterHooks = []/*createMatcher() 会通过这个函数将 route config 数据转化为 record.matcher 对象:{match,     //函数addRoute,  //函数 getRoutes, //函数addRoutes, //函数}*/this.matcher = createMatcher(options.routes || [], this)/*** 以下代码就是处理 options.mode:* 1、根据当前运行环境,以及浏览器的型号版本来决定最终的 mode 值。*    如果不支持 html5 history, 就会降级为 hash 模式。*    如果不是浏览器环境,则会改为 abstract 模式。** 2、根据 mode 创建对应的浏览历史history对象。*    this.$router.history = history;*///表示路由模式,默认是 hash 模式let mode = options.mode || 'hash'//fallback 表示降级。//如果当前环境不支持 html5 的 history 模式。那么就会退成为 hash 模式。this.fallback =mode === 'history' && !supportsPushState && options.fallback !== falseif (this.fallback) {mode = 'hash'}//如果不是在浏览器环境,则 mode 设置为 abstract。if (!inBrowser) {mode = 'abstract'}//设置 this.$router 的路由模式。this.mode = modeswitch (mode) {case 'history'://如果mode=history,就创建 history 模式的导航对象。//options.base 是 location.pathname 基准地址。this.history = new HTML5History(this, options.base)breakcase 'hash'://如果mode=hash,就创建 hash 模式的导航对象。this.history = new HashHistory(this, options.base, this.fallback)breakcase 'abstract'://如果mode=abstract,创建 abstract 模式的导航对象。//用于非浏览器环境。this.history = new AbstractHistory(this, options.base)breakdefault://如果不是生产环境,且 mode 不是上述的三种mode,则报红提示。if (process.env.NODE_ENV !== 'production') {assert(false, `invalid mode: ${mode}`)}}}/*match()  根据 raw 来匹配 route config,从而生成一个新的 route 对象。第一个参数: raw 是一个字符串形式的路径。第二个参数: 当前的 route 实例。第三个参数:  用于重定向的 redirectedFrom. 形式为: {  path:xxx, name:xxx, query: {}, params: {} }*/match(raw: RawLocation, current?: Route, redirectedFrom?: Location): Route {//调用 matcher 对象的 matcher 方法。return this.matcher.match(raw, current, redirectedFrom)}/** 获取当前的 route 对象。 相当于在 vue 页面的 this.$route*/get currentRoute(): ?Route {//如果 router 的 history 属性存在,则获取 history 保存的当前 route 对象。return this.history && this.history.current}/*init(); (1) 可以理解为每次 vue 实例创建时,对于 vue-router 的初始化。(2) 以及当第一个 vue 实例创建之后, 开始导航初始化工作。这个方法是在当 vue 实例被创建时,被 VueRouter.install() 中混入的 beforeCreate() 方法中执行的。第一个参数 app,表示当前正在创建的 vue 实例。*/init(app: any /* Vue component instance */) {//如果不是生产环境,判断 install.installed 是否已经有标记。表明 Vue.use(VueRouter) 是否执行过。process.env.NODE_ENV !== 'production' &&//如果 installed 标志不存在,那么表示没有走 Vue.use(VueRouter) 流程。assert(install.installed,`not installed. Make sure to call \`Vue.use(VueRouter)\` ` +`before creating root instance.`)//router 通过 apps 属性来记录所有创建过的 vue 实例。this.apps.push(app)//增加 vue 的 destoryed 钩子函数。app.$once('hook:destroyed', () => {//这个是对应的 vue 实例的 destroyed 函数内; this 是对应的 vue 实例。const index = this.apps.indexOf(app)//如果当前 vue 实力被记录到了 this.$router.apps 中, 就将其移除。if (index > -1) this.apps.splice(index, 1)//如果 this.app === app. 表明在删除最后一个 vue 实例。if (this.app === app) this.app = this.apps[0] || null//如果 this.app 为 null,则表示所有 vue 实例都已经被销毁。所以需要销毁 history。if (!this.app) this.history.teardown()})//如果 this.app 有值,则直接返回。则 this.app 指记录根 vue 实例。if (this.app) {return}/*以下的代码都是创建第一个 vue 实例的时候才会被调用的。  可以理解为 this.app 是指向 app.vue 这个文件的实例的。*///如果 this.app 不存在,则指向 app 实例。this.app = app//获取 this.$router mode 对应的 history 对象。const history = this.history//如果是 h5 的 history, 或者 hash 模式。if (history instanceof HTML5History || history instanceof HashHistory) {//操作初始化滚动const handleInitialScroll = (routeOrError) => {//表示即将要跳出的 route。const from = history.current/*针对前一个页面存在滚动条且滚动到了一定的位置,当路由跳转后发现滚动条的位置还保持在原来的位置。//savedPosition 为 null, 或者上次保存的坐标。const router = new VueRouter({routes: [xxxx],scrollBehavior( to, from, savedPosition ){if( savedPosition ){return savedPosition;}else {return { x:0, y:0 }}}})*///期望滚动的函数。const expectScroll = this.options.scrollBehavior//如果mode=history,且当前浏览器支持 h5 history, 则表示支持期望滚动函数。const supportsScroll = supportsPushState && expectScroll//routeOrError 存在 fullPath 属性, 且 supportsScroll 函数存在if (supportsScroll && 'fullPath' in routeOrError) {//routeOrError 表示要跳转的 routehandleScroll(this, routeOrError, from, false)}}//路径跳转成功,或者失败的回调。// 如果跳转成功,则传递的参数为: route。// 如果跳转失败,则传递的参数为: error。const setupListeners = (routeOrError) => {history.setupListeners()handleInitialScroll(routeOrError)}/*** 此次的跳转是针对浏览器地址栏上的 url 进行跳转。* 地址栏可能是根路径: http://localhost:8080/;也可能是某个网页的路径 http://localhost:8080/user/info;*///跳转history.transitionTo(//获取浏览器地址栏上的 url。//history.getCurrentLocation(): 返回的是访问地址字符串。history.getCurrentLocation(),//路径跳转成功的回调。setupListeners,//路径跳转失败的回调。setupListeners)}// this.$router.history 增加 route改变的事件监听。// 第一个参数: (route)=>{xxx} 会被记录到 history.cb 属性上。//   当 history.updateRoute(route) 被调用时,则 history.cb() 函数会被调用。history.listen((route) => {//如果 route 对象发生改变,那么每一个 vue 实例的中记录的 _route 都更新。即 this.$routethis.apps.forEach((app) => {app._route = route})})}/*增加 - 路由全局前置钩子beforeEach(): 用于增加 router 的全局路由钩子。钩子能拦截路由跳转。参数 fn 的形式就是: ( to, from, next )=>{第一个参数 to: 即将要进入的目标 用一种标准化的方式。第二个参数 from: 当前导航正要离开的路由 用一种标准化的方式。第三个参数 next: 如果 next(),则表示执行跳转。如果是 next("/login"),则跳转到指定页面。   }fn的返回值:(1)false: 取消当前的导航。如果浏览器的 URL 改变了(可能是用户手动或者浏览器后退按钮),那么 URL 地址会重置到 from 路由对应的地址。(2) 一个路由地址: 通过一个路由地址跳转到一个不同的地址,就像你调用 router.push() 一样,你可以设置诸如 replace: true 或name: 'home' 之类的配置。当前的导航被中断,然后进行一个新的导航,就和 from 一样。可以使用 router.beforeEach 注册一个全局前置守卫*/beforeEach(fn: Function): Function {return registerHook(this.beforeHooks, fn)}/*** 增加 - 路由全局解析钩子* (1) 可以用 router.beforeResolve 注册一个全局守卫。这和 router.beforeEach 类似,因为它在 每次导航时都会触发,但是确保在导航被确认之前,* 同时在所有组件内守卫和异步路由组件被解析之后,解析守卫就被正确调用.* (2)router.beforeResolve 是获取数据或执行任何其他操作(如果用户无法进入页面时你希望避免执行的操作)的理想位置。*/beforeResolve(fn: Function): Function {return registerHook(this.resolveHooks, fn)}/*增加 - 路由全局后置钩子。可以用 router.afterEach 注册全局后置钩子,然而和守卫不同的是,这些钩子不会接受 next 函数也不会改变导航本身:*/afterEach(fn: Function): Function {return registerHook(this.afterHooks, fn)}/*** 增加 - 路由初始化完成时的回调通知。* 可以使用 router.onReady 注册路由初始化完成之后的回调函数。*/onReady(cb: Function, errorCb?: Function) {this.history.onReady(cb, errorCb)}/*** 增加 - 路由发生错误的时候的回调*/onError(errorCb: Function) {this.history.onError(errorCb)}/*跳转到新的页面。*/push(location: RawLocation, onComplete?: Function, onAbort?: Function) {// $flow-disable-lineif (!onComplete && !onAbort && typeof Promise !== 'undefined') {return new Promise((resolve, reject) => {this.history.push(location, resolve, reject)})} else {this.history.push(location, onComplete, onAbort)}}/*** 重定向到新的页面。replace 会替换掉当前页面所在的浏览器历史记录。*/replace(location: RawLocation, onComplete?: Function, onAbort?: Function) {// $flow-disable-lineif (!onComplete && !onAbort && typeof Promise !== 'undefined') {return new Promise((resolve, reject) => {this.history.replace(location, resolve, reject)})} else {this.history.replace(location, onComplete, onAbort)}}/*$router.go() 函数。具体规则参考 html5 history 的 go() 函数。*/go(n: number) {this.history.go(n)}/*$router.back() 函数。 回退到上一页。*/back() {this.go(-1)}/*$router.forward() 函数,历史记录前进一页。*/forward() {this.go(1)}/*** 获取匹配到的组件数组。*/getMatchedComponents(to?: RawLocation | Route): Array<any> {/***  let route;*  if( ! to ){*   router = this.currentRoute;*  }else {*   router = to.matched ? to : this.resolve(to).route*  }**/const route: any = to? //to.matched? to: //根据 to 的路径参数,创建一个新的 route 对象。this.resolve(to).route: this.currentRoute//如果 route 不存在,则返回空数组。if (!route) {return []}//如果 route 存在,则返回 route 对应的 record 数组中的每一个 component。return [].concat.apply([],route.matched.map((m) => {/*** 我们配置 compnents 有几种形式:* 形式1:  component: User* 形式2:  component: import("xxxxxx")* 形式3:  components: { default: import("xxxx") }* 形式4:  components: { default: import("xxxx"), a: xxx, b:xxx }* 所以通过 map() 来拷贝每个 route config 中的 components 属性对象。*/return Object.keys(m.components).map((key) => {return m.components[key]})}))}/*** 解析*/resolve(to: RawLocation,current?: Route,append?: boolean): {location: Location,route: Route,href: string,// for backwards compatnormalizedTo: Location,resolved: Route,} {//如果 current 这个 route 不存在,则获取当前的路由 route 对象。current = current || this.history.current//将 to 对象转成标准的 { path:xxx, name:xxx, query:xxx, params:xxx } 的形式。//===> to 的 path 属性可能含有 query, hash 等数据。const location = normalizeLocation(to, current, append, this)//根据路径匹配相关配置,然后创建一个新的 route 对象。const route = this.match(location, current)//获取全路径(这个路径是替换完了动态参数的路径。)const fullPath = route.redirectedFrom || route.fullPath//获取路由的基准路径。const base = this.history.base//完整的 url。const href = createHref(base, fullPath, this.mode)return {//归一化的路径信息以及参数location,//新的 route 对象。route,//完整的 url 字符串。href,// for backwards compatnormalizedTo: location,resolved: route,}}/*返回一个数组,这个数组包含所有的 route config 对应的 record 对象。(用户所有的路由配置信息)*/getRoutes() {return this.matcher.getRoutes()}/*新增加一个路由。第一个参数:parentOrRoute,可以是父路由的 name 值; 也可以是要新添加的路由数据对象。第二个参数:要新添加的路由数据对象。*/addRoute(parentOrRoute: string | RouteConfig, route?: RouteConfig) {//将 route config 数据转换成为 record 对象,然后被添加到 parent record 中。this.matcher.addRoute(parentOrRoute, route)if (this.history.current !== START) {//这里感觉没多大必要,除非是没有增加这个 route config 之前,都不能匹配并且创建 route 实例。this.history.transitionTo(this.history.getCurrentLocation())}}/*批量新增路由。*/addRoutes(routes: Array<RouteConfig>) {if (process.env.NODE_ENV !== 'production') {warn(false,'router.addRoutes() is deprecated and has been removed in Vue Router 4. Use router.addRoute() instead.')}//跟 addRoute 流程一致。this.matcher.addRoutes(routes)if (this.history.current !== START) {this.history.transitionTo(this.history.getCurrentLocation())}}
}/*** 内部使用,被抽取出来的一个公共方法,用于各个钩子函数的添加到对应的消息队列中。*/
function registerHook(list: Array<any>, fn: Function): Function {//消息队列添加订阅函数。list.push(fn)//返回一个移除该订阅函数的方法。return () => {const i = list.indexOf(fn)if (i > -1) list.splice(i, 1)}
}/*** 创建一个 href 跳转路径。* 第一个参数: base 基础路径。* 第二个参数: 完整路径。* 第三个参数: 路由模式。*/
function createHref(base: string, fullPath: string, mode) {//如果是hash模式,那么 fullPath 是指 hash 地址。//如果是history模式,那么 fullPath 就是 location.pathname 部分。var path = mode === 'hash' ? '#' + fullPath : fullPath//如果基准路径存在,则拼接基准地址得到一个完整地址。return base ? cleanPath(base + '/' + path) : path
}VueRouter.install = install
VueRouter.version = '__VERSION__'
VueRouter.isNavigationFailure = isNavigationFailure
VueRouter.NavigationFailureType = NavigationFailureType
VueRouter.START_LOCATION = START// -- 我们使用 spa 应用, Vue 是没有挂到 window 上的。
//如果在浏览器中,且将 Vue 挂在到了 window 上。
if (inBrowser && window.Vue) {//走 vueRouter 的 install 流程。window.Vue.use(VueRouter)
}

vue-router3源码注视系列 /src/index.js相关推荐

  1. vue源码注释系列 /src/module/module.js

    import { forEachValue } from "../util";// Base data struct for store's module, package wit ...

  2. vue-router3 源码注释系列 /src/util/scroll.js

    /* @flow */import type Router from '../index' import { assert } from './warn' //getStateKey: 用于获取时间戳 ...

  3. vue-router3 源码注释系列 /src/util/query.js

    /* @flow */import { warn } from './warn'//判断字符为 !'()* 的正则表达式. const encodeReserveRE = /[!'()*]/g /* ...

  4. vue-router3 源码注释系列 /src/util/push-state.js

    /* @flow *///用于判断是否是浏览器环境 import { inBrowser } from './dom' //保存滚动的位置(x,y). import { saveScrollPosit ...

  5. vue-router3 源码注释系列 /src/util/path.js

    /* @flow *//*** resolvePath(): 解析路径* 第一个参数: 相对路径,要跳转路径的 pathname.* 第二个参数: 基准路径.* 第三个参数: 是否需要拼接基准地址.* ...

  6. vue 计算属性_lt;Vue 源码笔记系列6gt;计算属性 computed 的实现

    1. 前言 原文发布在语雀: <Vue 源码笔记系列6>计算属性 computed 的实现 · 语雀​www.yuque.com 上一章我们已经学习过 watch,这一章就来看一下计算属性 ...

  7. vue如何让一句代码只执行一次_lt;Vue 源码笔记系列4gt;异步更新队列与$nextTick...

    1. 前言 原文发布在语雀: <Vue 源码笔记系列4>异步更新队列与$nextTick · 语雀​www.yuque.com 上一章我们讲到了修改数据是如何触发渲染函数的观察者,最终调用 ...

  8. vue源码分析系列二:$mount()和new Watcher()的执行过程

    续vue源码分析系列一:new Vue的初始化过程 在initMixin()里面调用了$mount() if (vm.$options.el) {vm.$mount(vm.$options.el);/ ...

  9. vue修改节点class_Vue2.0 源码解读系列 来自 Vue 的神秘礼盒

    鄢栋,微医云服务团队前端工程师.有志成为一名全栈开发工程师甚至架构师,路漫漫,吾求索.生活中通过健身释放压力,思考问题. 目前 Vue3.0 打的很火热,都已经出了很多 Vue3.0 源码解析系列的博 ...

最新文章

  1. STARTUP报错:ORA-00205: error in identifying control file, check alert log for more info
  2. 【Keras】学习笔记(一)
  3. 经典论文复现 | 基于深度卷积网络的图像超分辨率算法
  4. 痞子衡嵌入式:第一本Git命令教程(0)- 索引
  5. BP神经网络python简单实现
  6. 数据库如何处理数据库太大_网络数据库中的数据处理
  7. python分类算法的应用_Python使用sklearn库实现的各种分类算法简单应用小结
  8. GitHub的实现是否是基于此语言的支持网络编程性呢?
  9. Maven Web项目配置Mybatis出现SqlSessionFactory错误的解决方案
  10. Tornado-Secure cookie and Session
  11. WCF分布式开发必备知识(4):Web Service(转)
  12. fiddler4写插件总结
  13. howler 音频插件使用
  14. java beetl输出demo_Beetl 快速入门
  15. 单播、组播(目标广播、多播)、广播、泛洪、洪泛介绍与比较
  16. 更改itunes备份路径【windows备份iphone数据】
  17. html如何关闭弹出qq,QQ登录时怎么关闭自动弹出的腾讯新闻迷你版窗口
  18. Word目录:【同一篇文档设置多个独立目录】详细过程
  19. vue-cli在webpack环境下怎样生成开发环境模板(适合初学者)
  20. STM32F4单片机读取光电编码器的小理解

热门文章

  1. 百度ueditor富文本--图片保存路径的配置以及上传到远程服务器
  2. 22. Vue keycodes按键修饰符
  3. 一文了解 rabbitMq 消息队列
  4. IAR 中设置 CSTACK HEAP作用
  5. 用spark自带的示例SparkPi测试scala和spark集群
  6. Webstorm学生认证
  7. List系列集合、泛型、Set系列集合、Collection系列集合使用场景总结
  8. 基于HTML+CSS实现的静态的电影网站【100010106】
  9. linux下安装python3报错_linux安装python3
  10. jq实现注册页面表单校验