前端模拟面试练习提升题

前言

当程序加载执行完vue-router文件,就执行new VueRouter()动作以及new Vue()动作,本篇文章就是探究这两个过程对于路由具体的处理逻辑。

具体分析

new VueRouter()

在第一篇文章中的实例中,调用new VueRouter()来创建router对象,VueRouter()具体的处理逻辑如下:
这里写图片描述
从上面的处理逻辑中可以看出,new VueRouter()创建路由对象就是初始化属性,而最为重要的就是mode模式问题了

mode模式支持’hash’、’history’、’abstract’,前两者是浏览器环境下,最后一个是支持Js的非浏览器环境


根据mode模式调用不同的构造函数生成不同的History对象,该对象是实现路由的核心对象之一

HashHistory构造函数

实际在加载解析vue-router时就已经执行了HashHistory

该函数本身是一个立即执行函数,主要的功能就是定义HashHistory相关的方法和构造函数以及实现继承History

在源码中History是父对象,无论是HashHistory、HTML5History还是AbstractHistory都继承自History
 
创建HashHistory最主要的功能点就是执行History构造函数
 
// History$$1就是History
History$$1.call(this, router, base);
而History对象定义了路由操作的最基本的操作,例如updateRoute等,而其构造函数就是定义需要用到的属性, 主要的属性如下:
 
router:当前路由对象
base:基本路径
current:当前路由模块,默认是START模块就是在加载解析是创建的path为/的默认路径
new Vue()
new Vue({
    router
}).$mount('#app')
 
这步操作实际上会调用VueRouter的beforeCreate生命周期函数
在上一篇文章中说了,在加载解析的过程中会调用install函数,而该函数中最重要的一点就是对所有组件全局混入beforeCreate和destroyed。
 
在new Vue()这步会调用beforeCreate生命周期函数,而这边是路由功能实现的触发点,具体看看该生命周期的处理逻辑:
 
    beforeCreate: function beforeCreate () {
      // 如果当前Vue实例存在router配置属性
      if (isDef(this.$options.router)) {
        // 当前Vue实例
        this._routerRoot = this;
        // 当前VueRouter实例对象
        this._router = this.$options.router;
        // 调用VueRouter.prototype.init
        this._router.init(this);
        // 定义响应属性_route
        Vue.util.defineReactive(this, '_route', this._router.history.current);
      } else {
        // 若父组件存在_routerRoot则当前Vue组件实例中_routerRoot与相同,否则就是当前Vue实例对象
        this._routerRoot = (this.$parent && this.$parent._routerRoot) || this;
      }
      registerInstance(this, this);
    },
 
 
这里Vue实例$options是存在router属性的,所以会调用VueRoute对象的init方法。
 
init
实际上在上一章就分析了这边的处理逻辑,就是定义Vue根实例对象,以及history的处理,而history的处理是init很关键的地方。
 
var history = this.history;
// history模式、hash模式的处理
if (history instanceof HTML5History) {
   history.transitionTo(history.getCurrentLocation());
} else if (history instanceof HashHistory) {
   var setupHashListener = function () {
     history.setupListeners();
   };
   history.transitionTo(
     history.getCurrentLocation(),
     setupHashListener,
     setupHashListener
   );
 }
 
 history.listen(function (route) {
   this$1.apps.forEach(function (app) {
     app._route = route;
   });
 });
 
 
从上面的代码可以看出,实际上是调用history对象的transitionTo、listen方法,实际上可以猜测出这两个方法的大概的功能:
 
transitionTo:路由切换
listen:监听
当然还是要看看它们的具体实现逻辑,首先来看看trsitionTo方法。
 
transitionTo
首先看看传递给transitionTo方法的参数,有三个:
 
history.getCurrentLocation()
另外两个参数都是history.setupListeners()
getCurrentLocation:是获取window.location.href中#之后的路径,就是路由路径
setupListeners:是监听popstate或hashchange事件
 
首先来看看setupListeners,这里是vue-router实现的核心
 
// 当前的history对象
var this$1 = this;
var router = this.router;
// 切换新路由的页面滚动位置的处理,在支持pushState的前提下
var expectScroll = router.options.scrollBehavior;
var supportsScroll = supportsPushState && expectScroll;
 
if (supportsScroll) {
  setupScroll();
}
 
// 监听popstate或hashchange事件
window.addEventListener(supportsPushState ? 'popstate' : 'hashchange', 
function () {
  // 获取当前路由
  var current = this$1.current;
  if (!ensureSlash()) {
    return
  }
  // 路由切换
  this$1.transitionTo(getHash(), function (route) {
    if (supportsScroll) {
      handleScroll(this$1.router, route, current, true);
    }
    // hash模式下,实际上是执行window.location.replace方法
    if (!supportsPushState) {
       replaceHash(route.fullPath);
    }
  });
});
 
 
从上面可知在实现路由切换都涉及到了transitionTo方法,就看看transitionTo的实现逻辑。
 
transitionTo内部实际上是调用confirmTransition方法
 
confirmTransition方法主要的点就是:
 
判断是否是相同模块,做具体的处理
 
应用beforeRouterEnter等钩子
 
执行uploadRoute,更新当前路由
 
执行传入的函数,实现注册监听onhashchange事件
 
listen
该方法就是替换Vue实例中的_route响应属性
 
总结
上面的总结就是分析了整个大概流程,VueRouter的实现细节蛮多的,实际上通过上面的基本知晓VueRouter整个流程:
 
new VueRouter()实际上主要就是history对象的实现,该对象是实现路由操作的核心
 
new Vue()时会触发组件的beforeCreate生命周期,调用VueRouter的init方法,完成默认路由的切换以及路由的监听
 
下一篇文章会分析路由切换时的处理流程。
————————————————