前言【vue-router源码】系列文章带你从中了解vue-router的具体实现0.本系列文章源码参考vue-routerv4.0.15。源码地址:https://github.com/vuejs/router阅读本文的前提是你最好了解vue-router的基本使用。如果没有使用过,可以通过vue-router官网进行学习。本文将介绍router.go、router.back、router.forward的实现。使用go函数可以让你在历史中向前或向后移动。如果指定步数>0,则表示前进;<0表示向后。router.go(-2)router.back()//相当于router.go(-1)router.forward()//相当于router.go(1)gogo接收一个参数delta,表示相对移动到当前页多少步,负数表示后退,正数表示前进。constgo=(delta:number)=>routerHistory.go(delta)会调用routerHistory.go中的history.go,然后触发popstate监听函数。如果你之前看过createWebHistory的分析就会知道,在createWebHistory中通过useHistoryListeners创建historyListeners时,会注册一个popstate监听函数,调用history.go后会触发这个监听函数。//文件位置:src/history/html5.tsuseHistoryListenersmethodwindow.addEventListener('popstate',popStateHandler)constpopStateHandler:PopStateListener=({state,}:{state:StateEntry|null})=>{//当前位置,stringconstto=createCurrentLocation(base,location)constfrom:HistoryLocation=currentLocation.valueconstfromState:StateEntry=historyState.valueletdelta=0//如果没有状态//关于为什么状态可能为空,请参考to:https://developer.mozilla.org/zh-CN/docs/Web/API/Window/popstate_eventif(state){currentLocation.value=tohistoryState.value=state//如果监听器暂停,并且暂停时的state是from,直接returnif(pauseState&&pauseState===from){pauseState=nullreturn}//计算移动步数delta=fromState?state.position-fromState.position:0}else{replace(to)}//循环调用监听函数listeners.forEach(listener=>{listener(currentLocation.value,from,{delta,type:NavigationType.pop,direction:三角洲?增量>0?NavigationDirection.forward:NavigationDirection.back:NavigationDirection.unknown,})})}可以看到listeners中的监听器会在监听函数结束时循环调用,那么什么是监听器呢?什么时候添加的?上一篇介绍install的实现时,其中一个很重要的操作就是根据地址栏中的url进行第一次跳转,第一次跳转是通过调用push方法完成的,因为push会调用pushWidthRedirect方法,在pushWidthRedirect的最后会执行finalizeNavigation(不管中间有没有reject错误)。在finalizeNavigation结束时,将调用markAsReady方法。函数markAsReady(err?:E):E|void{if(!ready){//如果发生错误仍未就绪ready=!errsetupListeners()readyHandlers.list().forEach(([resolve,reject])=>(err?reject(err):resolve()))readyHandlers.reset()}returnerr}markAsReady中调用了setupListeners的方法。在这个方法中,会调用routerHistory.listen()来增加一个功能。让removeHistoryListener:undefined|空|(()=>void)functionsetupListeners(){//如果有removeHistoryListener,说明已经添加了监听器if(removeHistoryListener)return//调用routerHistory.listen添加监听器函数,routerHistory.listen返回一个RemovethislistenerfunctionremoveHistoryListener=routerHistory.listen((to,_from,info)=>{consttoLocation=resolve(to)asRouteLocationNormalized//判断是否有重定向constshouldRedirect=handleRedirectRecord(toLocation)if(shouldRedirect){pushWithRedirect(assign(shouldRedirect,{replace:true}),toLocation).catch(noop)return}pendingLocation=toLocationconstfrom=currentRoute.value//从滚动位置保存if(isBrowser){saveScrollPosition(getScrollKey(from.fullPath,info.delta),computeScrollPosition())}navigate(toLocation,from).catch((error:NavigationFailure|NavigationRedirectError)=>{//如果(isNavigationFailure(error,ErrorTypes.NAVIGATION_ABORTED|ErrorTypes.NAVIGATION_CANCELLED)){returnerror}//在hook中重定向{//如果钩子中的导航被取消或者导航是多余的,退一步){routerHistory.go(-1,false)}}).catch(noop)returnPromise.reject()}//恢复历史,但不触发监听器if(info.delta)routerHistory.go(-info.delta,false)//无法识别的错误,移交给全局错误处理程序returntriggerError(error,toLocation,from)}).then((failure:NavigationFailure|void)=>{failure=failure||finalizeNavigation(toLocationasRouteLocationNormalizedLoaded,from,false)if(failure){//如果有错误提示,返回原位置,但不触发监听if(info.delta){routerHistory.go(-info.delta,false)}elseif(info.type===NavigationType.pop&&isNavigationFailure(failure,ErrorTypes.NAVIGATION_ABORTED|ErrorTypes.NAVIGATION_DUPLICATED)){//错误类型时导航被取消或冗余,fallback历史记录,但不触发监听routerHistory.go(-1,false)}}//触发全局afterEachhooktriggerAfterEach(toLocationasRouteLocationNormalizedLoaded,from,failure)}).catch(noop)})}可以看到这个监听函数和push的过程很像。与push不同的是,在触发监听的时候,一旦出现一些错误信息(比如导航被取消,导航冗余,位置错误),需要将历史记录回滚到对应的位置去执行流程:backback,回滚一条历史记录,相当于go(-1)。constrouter={//...back:()=>go(-1),//...}forwardforward,转发一条历史记录,相当于go(1)。constrouter={//...forward:()=>go(1),//...}总结go、back、forward方法,最后通过调用history.go方法触发popstate事件(popstate中的监听函数是在第一个路由跳转处添加的),popstate事件中的过程和push过程很相似。与push不同的是,一旦出现一些错误信息(比如导航被取消,导航时冗余导航,位置错误),需要回滚历史到对应的位置。