Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537 Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537 成人午夜性视频欧美成人,亚洲看片网站,性bbbb美女

          整合營銷服務商

          電腦端+手機端+微信端=數據同步管理

          免費咨詢熱線:

          VS Code常用插件

          VS Code常用插件
          1. .日常安利 VS code

            VS vode特點:

          開源,免費;

          自定義配置

          集成git

          智能提示強大

          支持各種文件格式(html/jade/css/less/sass/xml)

          調試功能強大

          各種方便的快捷鍵

          強大的插件擴展

            對前端這么友好,沒理由不用。


          Visual Studio Code(VScode )官網 :https://code.visualstudio.com/

          Visual Studio Code(VScode )github地址 :https://github.com/Microsoft/vscode

          二.怎么安裝插件?

          方法一:

          按F1或Ctrl+Shift+p,輸入extensions,點擊第一個就可以

          方法二:

          ctrl + P 然后輸入 >ext install

          方法三:

          點擊圖中位置

          三.插件合集

          插件官網:https://marketplace.visualstudio.com/

            每一個插件名都超鏈接到官網,注意查看

          a.配置類插件:

          1.Settings Sync

          最好用的插件,沒有之一,一臺電腦配置好之后,其它的幾臺電腦都不用配置。新機器登錄一下就搞定了。再也不用折騰環境了,

          使用GitHub Gist同步多臺計算機上的設置,代碼段,主題,文件圖標,啟動,鍵綁定,工作區和擴展。

          2.Debugger for Chrome

          從VS Code調試在Google Chrome中運行的JavaScript代碼。

          用于在Google Chrome瀏覽器或支持Chrome DevTools協議的其他目標中調試JavaScript代碼的VS Code擴展。

          3.beautify

          格式化代碼工具

          美化javascript,JSON,CSS,Sass,和HTML在Visual Studio代碼。

          4.Atuo Rename Tag

          修改 html 標簽,自動幫你完成頭部和尾部閉合標簽的同步修改


          5.中文(簡體)語言包

          Chinese (Simplified) Language Pack for Visual Studio Code

          將界面轉換為中文,對英語不好的人,非常友好。例如我。。。


          6.Code Spell Checker

          代碼拼寫檢查器

          一個與camelCase代碼配合良好的基本拼寫檢查程序。

          此拼寫檢查程序的目標是幫助捕獲常見的拼寫錯誤,同時保持誤報數量較低。

          7.vscode-icons

          顯示Visual Studio代碼的圖標,目前該插件已被vscode內部支持:"文件" -> "首選項" -> "文件圖標主題"

          8.guides

          顯示代碼對齊輔助線,很好用

          9.Rainbow Brackets

          為圓括號,方括號和大括號提供彩虹色。這對于Lisp或Clojure程序員,當然還有JavaScript和其他程序員特別有用。

          效果如下:

          10.Bracket Pair Colorizer

          用于著色匹配括號

          11.Indent-Rainbow

          用四種不同顏色交替著色文本前面的縮進

          12.filesize

          在狀態欄中顯示當前文件大小,點擊后還可以看到詳細創建、修改時間

          13.Import Cost

          對引入的計算大小


          14.Path Intellisense

          可自動填充文件名。


          15.WakaTime

          從您的編程活動自動生成的度量標準,見解和時間跟蹤。

          16.GitLens

          git日志查看插件

          GitLens 增強了 Visual Studio Code 中內置的 Git 功能。例如 commits 搜索,歷史記錄和和查看代碼作者身份,還能通過強大的比較命令獲得有價值的見解等等

          17..REST Client

          REST客戶端允許您直接發送HTTP請求并在Visual Studio Code中查看響應。

          18.Npm Intellisense

          用于在 import 語句中自動填充 npm 模塊

          require 時的包提示(最新版的vscode已經集成此功能)


          19.Azure Storage

          VS Code的Azure存儲擴展允許您部署靜態網站并瀏覽Azure Blob容器,文件共享,表和隊列。按照本教程從VS Code部署Web應用程序到Azure存儲。

          20.Project Manager

          它可以幫助您輕松訪問項目,無論它們位于何處。不要再錯過那些重要的項目了。您可以定義自己的收藏項目,或選擇自動檢測VSCode項目,Git,Mercurial和SVN存儲庫或任何文件夾。

          從版本8開始,您就有了專門的項目活動欄!

          以下是Project Manager提供的一些功能:

          將任何項目保存為收藏夾

          自動檢測VSCode,GIT中,水銀或SVN存放區

          在相同或新窗口中打開項目

          識別已刪除/重命名的項目

          一個狀態欄標識當前項目

          專門的活動欄

          21.Language Support for Java(TM) by Red Hatredhat.java

          這個插件,這個下載次數,安裝就對了。


          22.Todo Tree

          此擴展可以快速搜索(使用ripgrep)您的工作區以獲取TODO和FIXME等注釋標記,并在資源管理器窗格的樹視圖中顯示它們。單擊樹中的TODO將打開文件并將光標放在包含TODO的行上。

          找到的TODO也可以在打開的文件中突出顯示。

          b.VS code 主題集合

          1.Night Owl

          一個非常適合夜貓子的 VS Code 主題。像是為喜歡深夜編碼的人精心設計的。


          2.Atom One Dark Theme

          一個基于Atom的黑暗主題

          3.Dracula Official

          官方吸血鬼主題,博主用的就是這款,很漂亮

          4.One Dark Pro

          Atom標志性的One Dark主題,也是VS Code下載次數最多的主題之一!

          5.Bimbo

          簡約而現代的神奇海洋主題


          c.代碼提示提示類

          1.HTML Snippets

          完整的HTML代碼提示,包括HTML5

          2.HTML CSS Support

          在 html 標簽上寫class 智能提示css樣式

          3.jQuery Code Snippets

          jQuery代碼提示

          超過130個用于JavaScript代碼的jQuery代碼片段。

          只需鍵入字母'jq'即可獲得所有可用jQuery代碼片段的列表。


          4.HTMLHint

          html代碼檢測,支持html5

          d.語言相關

          1.C#

          適用于.NET Core的輕量級開發工具。

          偉大的C#編輯支持,包括語法突出顯示,智能感知,轉到定義,查找所有引用等。

          調試支持.NET Core(CoreCLR)。注意:不支持單聲道調試。桌面CLR調試支持有限。

          支持Windows,macOS和Linux上的project.json和csproj項目。

          2.CodeMetrics

          計算TypeScript / JavaScript文件的復雜性。

          3.VUE插件

            vetur    語法高亮、智能感知、Emmet等

            VueHelper   snippet代碼片段

          ESLint   將ESLint JavaScript集成到VS代碼中。

            prettier 代碼規范性插件

          4. Java Extension Pack

          它是一組流行的擴展,可以幫助在Visual Studio Code中編寫,測試和調試Java應用程序。查看VS Code中的Java以開始使用。

          作者:殺死哪個崇明島人

          https://www.bilibili.com/read/cv11058549

          出處: bilibili

          柜 U 位管理是一項突破性創新技術--繼承了 RFID 標簽(電子標簽)的優點的同時,完全解決了 RFID 技術(非接觸式的自動識別技術)在機房 U 位資產監控場應用景中的四大缺陷,采用工業互聯網云平臺監控機房 U 位的方法,具有高可靠性、高準確性、精準定位、免維護的特點,滿足了 U 位級實時監控、智能運維閉環管理的需求。設備上架、下架與遷移,自動變更和實時記錄,(用戶評價):部署工業互聯網云平臺監控機房 U 位后節省了 99% 的登記變更記錄的時間,而且實現了變更后數據 100% 的準確,在這之前是難以想象的,真正實現運維管理最后的工作。

          整個 Demo 由最左側的樹,中間部分的列表以及右邊的拓撲圖整體構成,為了讓整個布局干凈一點,這里結合 splitView 和 borderPane 兩種布局方式來進行。首先將場景分為左右兩個部分,左邊為樹,右邊是列表和拓撲圖的組合:

          布局結束記得將最外層組件的最底層 div 添加到 body 中,HT 的組件一般都會嵌入 BorderPane、SplitView 和 TabView 等容器中使用,而最外層的HT組件則需要用戶手工將 getView() 返回的底層 div 元素添加到頁面的 DOM 元素中,這里需要注意的是,當父容器大小變化時,如果父容器是 BorderPane 和 SplitView 等這些HT預定義的容器組件,則HT的容器會自動遞歸調用孩子組件 invalidate 函數通知更新。但如果父容器是原生的 html 元素, 則 HT 組件無法獲知需要更新,因此最外層的 HT 組件一般需要監聽 window 的窗口大小變化事件,調用最外層組件 invalidate 函數進行更新。為了最外層組件加載填充滿窗口的方便性,HT 的所有組件都有 addToDOM 函數,其實現邏輯如下,其中 iv 是 invalidate 的簡寫:

          右邊的拓撲圖部分是在監聽選中變化事件的時候更新的,當然,初始化設置的選中樹上的第一個節點就觸發了選中變化事件:

          上面代碼中 splitView.setRightView 函數意為設置右側組件,有了這個函數,我就可以動態地改變 spliteView 組件中的右側組件了。

          端路由 前端路由是后來發展到SPA(單頁應用)時才出現的概念。 SPA 就是一個WEB項目只有一個 HTML 頁面,一旦頁面加載完成,SPA 不會因為用戶的操作而進行頁面的重新加載或跳轉。 前端路由在SPA項目中是必不可少的,頁面的跳轉、刷新都與路由有關,通過不同的url顯示相應的頁面。 優點:前后端的徹底分離,不刷新頁面,用戶體驗較好,頁面持久性較好。 后端路由 當在地址欄切換不同的url時,都會向服務器發送一個請求,服務器接收并響應這個請求,在服務端拼接好html文件返回給頁面來展示。 優點:減輕了前端的壓力,html都由后端拼接; 缺點:依賴于網絡,網速慢,用戶體驗很差,項目比較龐大時,服務器端壓力較大, 不能在地址欄輸入指定的url訪問相應的模塊,前后端不分離。 路由模式 前端路由實現起來其實很簡單,本質是監聽 URL 的變化,然后匹配路由規則,在不刷新的情況下顯示相應的頁面。 hash模式(對應HashHistory)

          • 把前端路由的路徑用井號 # 拼接在真實 url 后面的模式,但是會覆蓋錨點定位元素的功能,通過監聽 URL 的哈希部分變化,相應地更新頁面的內容。
          • 前端路由的處理完全在客戶端進行,在路由發生變化時,只會改變 URL 中的哈希部分(井號 # 后面的路徑),且不會向服務器發送新的請求,而是觸發 onhashchange 事件。
          • hash 只有#符號之前的內容才會包含在請求中被發送到后端,如果 nginx 沒有匹配得到當前的 url 也沒關系。hash 永遠不會提交到 server 端。
          • hash值的改變,都會在瀏覽器的訪問歷史中增加一個記錄,所以可以通過瀏覽器的回退、前進按鈕控制hash的切換。
          • hash 路由不會造成 404 頁面的問題,因為所有路由信息都在客戶端進行解析和處理,服務器只負責提供應用的初始 HTML 頁面和靜態資源,不需要關心路由的匹配問題。
          // onhashchage事件,可以在window對象上監聽這個事件
          window.onhashchange=function(event){
            console.log(event.oldURL, event.newURL)
            let hash=location.hash.slice(1)
          }


          • 通過location.hash修改hash值,觸發更新。
          • 通過監聽hashchange事件監聽瀏覽器前進或者后退,觸發更新。

          history模式 (對應HTML5History)

          • 是 html5 新推出的功能,比 Hash url 更美觀
          • 在 history 模式下瀏覽器在刷新頁面時,會按照路徑發送真實的資源請求。如果 nginx 沒有匹配得到當前的 url ,就會出現 404 的頁面。
          • 在使用 history 模式時,需要通過服務端支持允許地址可訪問,如果沒有設置,就很容易導致出現 404 的局面。
          • 改變url: history 提供了 pushState 和 replaceState 兩個方法來記錄路由狀態,這兩個方法只改變 URL 不會引起頁面刷新。
          • 監聽url變化:通過 onpopstate 事件監聽history變化,在點擊瀏覽器的前進或者后退功能時觸發,在onpopstate 事件中根據狀態信息加載對應的頁面內容。
          history.replaceState({}, null, '/b') // 替換路由
          history.pushState({}, null, '/a') // 路由壓棧,記錄瀏覽器的歷史棧 不刷新頁面
          history.back() // 返回
          history.forward() // 前進
          history.go(-2) // 后退2次

          history.pushState 修改瀏覽器地址,而頁面的加載是通過 onpopstate 事件監聽實現,加載對應的頁面內容,完成頁面更新。

          // 頁面加載完畢 first.html
          history.pushState({page: 1}, "", "first.html");
          
          window.onpopstate=function(event) {
            // 根據當前 URL 加載對應頁面
            loadPage(location.pathname); 
          };
          
          // 點擊跳轉到 second.html
          history.pushState({page: 2}, "", "second.html");
          
          function loadPage(url) {
            // 加載 url 對應頁面內容
            // 渲染頁面
          }

          onpopstate 事件是瀏覽器歷史導航的核心事件,它標識了頁面狀態的變化時機。通過監聽這個時機,根據最新的狀態信息更新頁面 當使用 history.pushState() 或 history.replaceState() 方法修改瀏覽器的歷史記錄時,不會直接觸發 onpopstate 事件。 但是,可以在調用這些方法時將數據存儲在歷史記錄條目的狀態對象中, onpopstate 事件在處理程序中訪問該狀態對象。這樣,就可以在不觸發 onpopstate 事件的情況下更新頁面內容,并獲取到相應的狀態值。 history 模式下 404 頁面的處理 在 history 模式下,瀏覽器會向服務器發起請求,服務器根據請求的路徑進行匹配: 如果服務器無法找到與請求路徑匹配的資源或路由處理器,服務器可以返回 /404 路由,跳轉到項目中配置的 404 頁面,指示該路徑未找到。 對于使用歷史路由模式的單頁應用(SPA),通常會在服務器配置中添加一個通配符路由,將所有非靜態資源的請求都重定向到主頁或一個自定義的 404 頁面,以保證在前端處理路由時不會出現真正的 404 錯誤頁面。 在項目中配置對應的 404 頁面:

          export const publicRoutes=[
            {
              path: '/404',
              component: ()=> import('src/views/404/index'),
            },
          ]

          vueRouter Vue Router 是 Vue.js 的官方路由。它與 Vue.js 核心深度集成,允許你在 Vue 應用中構建單頁面應用(SPA),并且提供了靈活的路由配置和導航功能。讓用 Vue.js 構建單頁應用變得輕而易舉。功能包括:

          • 路由映射:可以將 url 映射到 Vue組件,實現不同 url 對應不同的頁面內容。
          • 嵌套路由映射:可以在路由下定義子路由,實現更復雜的頁面結構和嵌套組件的渲染。
          • 動態路由:通過路由參數傳遞數據。你可以在路由配置中定義帶有參數的路由路徑,并通過 $route.params 獲取傳遞的參數。
          • 模塊化、基于組件的路由配置:路由配置是基于組件的,每個路由都可以指定一個 Vue 組件作為其頁面內容,將路由配置拆分為多個模塊,在需要的地方引入。。
          • 路由參數、查詢、通配符:通過路由參數傳遞數據,實現頁面間的數據傳遞和動態展示。
          • 導航守衛:Vue Router 提供了全局的導航守衛和路由級別的導航守衛,可以在路由跳轉前后執行一些操作,如驗證用戶權限、加載數據等。
          • 展示由 Vue.js 的過渡系統提供的過渡效果:可以為路由組件添加過渡效果,使頁面切換更加平滑和有動感。
          • 細致的導航控制:可以通過編程式導航(通過 JavaScript 控制路由跳轉)和聲明式導航(通過 組件實現跳轉)實現頁面的跳轉。
          • 路由模式設置:Vue Router 支持兩種路由模式:HTML5 history 模式或 hash 模式
          • 可定制的滾動行為:當頁面切換時,Vue Router 可以自動處理滾動位置。定制滾動行為,例如滾動到頁面頂部或指定的元素位置。
          • URL 的正確編碼:Vue Router 會自動對 URL 進行正確的編碼

          路由組件

          • **router-link:**通過 router-link 創建鏈接 其本質是a標簽,這使得 Vue Router 可以在不重新加載頁面的情況下更改 URL,處理 URL 的生成以及編碼。
          • **router-view:**router-view 將顯示與 url 對應的組件。

          $router$route $route: 是當前路由信息對象,獲取和當前路由有關的信息。 route 為屬性是只讀的,里面的屬性是 immutable (不可變) 的,不過可以通過 watch 監聽路由的變化。

          fullPath: ""  // 當前路由完整路徑,包含查詢參數和 hash 的完整路徑
          hash: "" // 當前路由的 hash 值 (錨點)
          matched: [] // 包含當前路由的所有嵌套路徑片段的路由記錄 
          meta: {} // 路由文件中自賦值的meta信息
          name: "" // 路由名稱
          params: {}  // 一個 key/value 對象,包含了動態片段和全匹配片段就是一個空對象。
          path: ""  // 字符串,對應當前路由的路徑
          query: {}  // 一個 key/value 對象,表示 URL 查詢參數。跟隨在路徑后用'?'帶的參數

          $router是 vueRouter 實例對象,是一個全局路由對象,通過 this.$router 訪問路由器, 可以獲取整個路由文件或使用路由提供的方法。

          // 導航守衛
          router.beforeEach((to, from, next)=> {
            /* 必須調用 `next` */
          })
          router.beforeResolve((to, from, next)=> {
            /* 必須調用 `next` */
          })
          router.afterEach((to, from)=> {})
          
          動態導航到新路由
          router.push
          router.replace
          router.go
          router.back
          router.forward
          
          

          routes 是 router 路由實例用來配置路由對象 可以使用路由懶加載(動態加載路由)的方式

          • 把不同路由對應的組件分割成不同的代碼塊,當路由被訪問時才去加載對應的組件 即為路由的懶加載,可以加快項目的加載速度,提高效率
          const router=new VueRouter({
            routes: [
              {
                path: '/home',
                name: 'Home',
                component:()=import('../views/home')
          		}
            ]
          })
          
          

          vueRouter的使用

          頁面中路由展示位置

          <div id="app">
            <!-- 添加路由 -->
            <!-- 會被渲染為 <a href="#/home"></a> -->
            <router-link to="/home">Home</router-link>
            <router-link to="/login">Login</router-link>
            <!-- 展示路由的內容 -->
            <router-view></router-view>
          </div>
          
          

          路由模塊 引入 vue-router,使用 Vue.use(VueRouter) 注冊路由插件 定義路由數組,并將數組傳入VueRouter 實例,并將實例暴露出去

          import Vue from 'vue'
          import VueRouter from 'vue-router'
          import { hasVisitPermission, isWhiteList } from './permission'
          
          // 注冊路由組件
          Vue.use(VueRouter)
          
          // 創建路由: 每一個路由規則都是一個對象
          const routers=[
            // path 路由的地址
            // component 路由的所展示的組件
            {
                path: '/',
                // 當訪問 '/'的時候 路由重定向 到新的地址 '/home'
                redirect: '/home',
            },     
            {
                path: '/home',
                component: home,
            },
            {
                path: '/login',
                component: login,
            },
          ],
          
          // 實例化 VueRouter 路由
          const router=new VueRouter({
            mode: 'history',
            base: '/',
            routers
          })
          
          // 路由守衛
          router.beforeEach(async (to, from, next)=> {
            // 清除面包屑導航數據
            store.commit('common/SET_BREAD_NAV', [])
            // 是否白名單
            if (isWhiteList(to)) {
              next()
            } else {
              // 未登錄,先登錄
              try {
                if (!store.state.user.userInfo) {
                  await store.dispatch('user/getUserInfo')
                }
          
                // 登錄后判斷,是否有訪問頁面的權限
                if (!hasVisitPermission(to, store.state.user.userInfo)) {
                  next({ path: '/404' })
                } else {
                  next()
                }
              } catch (err) {
                $error(err)
              }
            }
          })
          
          export default router
          
          

          在 main.js 上掛載路由 將VueRouter實例引入到main.js,并注冊到根Vue實例上

          import router from './router'
          
          new Vue({
            router,
            store,
            render: h=> h(App),
          }).$mount('#app')
          
          

          動態路由 我們經常需要把某種模式匹配到的所有路由,全都映射到同個組件。例如,我們有一個 User 組件,對于所有 ID 各不相同的用戶,都要使用這個組件來渲染。我們可以在 vueRrouter 的路由路徑中使用“動態路徑參數”(dynamic segment) 來達到這個效果。

          • 動態路由的創建,主要是使用 path 屬性過程中,使用動態路徑參數,路徑參數 用冒號 : 表示。

          當一個路由被匹配時,它的 params 的值將在每個組件中以 this.$route.query 的形式暴露出來。因此,我們可以通過更新 User 的模板來呈現當前的用戶 ID:

          const routes=[
            {
              path: '/user/:id'
              name: 'User'
              components: User
          	}
          ]
          
          

          _vue-router _通過配置 _params __query _來實現動態路由

          params 傳參

          • 必須使用 命名路由 name 傳值
          • 參數不會顯示在 url 上
          • 瀏覽器強制刷新時傳參會被清空
          // 傳遞參數
          this.$router.push({
            name: Home,
            params: {
              number: 1 ,
              code: '999'
            }
          })
          // 接收參數
          const p=this.$route.params

          query 傳參

          • 可以用 name 也可以使用 path 傳參
          • 傳遞的參數會顯示在 url 上
          • 頁面刷新是傳參不會丟失
          // 方式一:路由拼接
          this.$router.push('/home?username=xixi&age=18')
          
          // 方式二:name + query 傳參
          this.$router.push({
            name: Home,
            query: {
              username: 'xixi',
              age: 18
          	}
          })
          
          
          // 方式三:path + name 傳參
          this.$router.push({
            path: '/home',
            query: {
              username: 'xixi',
              age: 18
          	}
          })
          
          // 接收參數
          const q=this.$route.query
          
          

          keep-alive keep-alive是vue中的內置組件,能在組件切換過程中將狀態保留在內存中,防止重復渲染DOM。 keep-alive 包裹動態組件時,會緩存不活動的組件實例,而不是銷毀它們。 和 transition 相似,keep-alive 是一個抽象組件:它自身不會渲染一個 DOM 元素,也不會出現在組件的父組件鏈中。 keep-alive 可以設置以下props屬性:

          • include - 字符串或正則表達式。只有名稱匹配的組件會被緩存
          • exclude - 字符串或正則表達式。任何名稱匹配的組件都不會被緩存
          • max - 數字。最多可以緩存多少組件實例

          在不緩存組件實例的情況下,每次切換都會重新 render,執行整個生命周期,每次切換時,重新 render,重新請求,必然不滿足需求。 會消耗大量的性能 keep-alive 的基本使用 只是在進入當前路由的第一次render,來回切換不會重新執行生命周期,且能緩存router-view的數據。 通過 include 來判斷是否匹配緩存的組件名稱: 匹配首先檢查組件自身的 name 選項,如果 name 選項不可用,則匹配它的局部注冊名稱 (父組件 components 選項的鍵值),匿名組件不能被匹配

          <keep-alive>
          	<router-view></router-view>
          </keep-alive>
          
          <keep-alive include="a,b">
            <component :is="view"></component>
          </keep-alive>
          
          <!-- 正則表達式 (使用 `v-bind`) -->
          <keep-alive :include="/a|b/">
            <component :is="view"></component>
          </keep-alive>
          
          <!-- 數組 (使用 `v-bind`) -->
          <keep-alive :include="['a', 'b']">
            <component :is="view"></component>
          </keep-alive>

          路由配置 keepAlive

          在路由中設置 keepAlive 屬性判斷是否需要緩存

          {
            path: 'list',
            name: 'itemList', // 列表頁
            component (resolve) {
              require(['@/pages/item/list'], resolve)
           	},
             meta: {
              keepAlive: true,
              compName: 'ItemList'
              title: '列表頁'
             }
          }
          
          {
            path: 'management/class_detail/:id/:activeIndex/:status',
            name: 'class_detail',
            meta: {
              title: '開班詳情',
              keepAlive: true,
              compName: 'ClassInfoDetail',
              hideInMenu: true,
            },
            component: ()=> import('src/views/classManage/class_detail.vue'),
          },

          使用

          <div id="app" class='wrapper'>
            <keep-alive>
                <!-- 需要緩存的視圖組件 --> 
                <router-view v-if="$route.meta.keepAlive"></router-view>
             </keep-alive>
              <!-- 不需要緩存的視圖組件 -->
             <router-view v-if="!$route.meta.keepAlive"></router-view>
          </div>

          keepAlive 對生命周期的影響 設置緩存后組件加載的生命周期會新增 actived 與 deactived

          • 首次進入組件時也會觸發 actived 鉤子函數:beforeRouteEnter > beforeCreate > created> beforeMount > beforeRouteEnter 的 next 回調> mounted > activated > ... ... > beforeRouteLeave > deactivated
          • 再次進入組件時直接獲取actived的組件內容:beforeRouteEnter >activated > ... ... > beforeRouteLeave > deactivated

          keep-alive 組件監聽 include 及 exclude 的緩存規則,若發生變化則執行 pruneCache (遍歷cache 的name判斷是否需要緩存,否則將其剔除) 且 keep-alive 中沒有 template,而是用了 render,在組件渲染的時候會自動執行 render 函數,

          • 若命中緩存則直接從緩存中拿 vnode 的組件實例,
          • 若未命中緩存且未被緩存過則將該組件存入緩存,
          • 當緩存數量超出最大緩存數量時,刪除緩存中的第一個組件。

          動態路由緩存的的具體表現在:

          • 由動態路由配置的路由只能緩存一份數據。
          • keep-alive 動態路由只有第一個會有完整的生命周期,之后的路由只會觸發 actived 和 deactivated這兩個鉤子。
          • 一旦更改動態路由的某個路由數據,期所有同路由下的動態路由數據都會同步更新。

          如何刪除 keep-alive 中的緩存 vue2 中清除路由緩存

          在組件內可以通過 this 獲取 vuerouter 的緩存
          vm.$vnode.parent.componentInstance.cache

          或者通過 ref 獲取 外級 dom

          添加圖片注釋,不超過 140 字(可選)

          <template>
            <el-container id="app-wrapper">
              <Aside />
              <el-container>
                <el-header id="app-header" height="45px">
                  <Header @removeCacheRoute="removeCacheRoute" />
                </el-header>
                <!-- {{ includeViews }} -->
                <el-main id="app-main">
                  <keep-alive :include="includeViews">
                    <router-view ref="routerViewRef" :key="key" />
                  </keep-alive>
                </el-main>
              </el-container>
            </el-container>
          </template>
          
          <script>
          import Aside from './components/Aside'
          import Header from './components/Header'
          import { mapGetters } from 'vuex'
          export default {
            name: 'Layout',
            components: {
              Aside,
              Header,
            },
            data () {
              return {
              }
            },
            computed: {
              ...mapGetters(['cacheRoute', 'excludeRoute']),
              includeViews () {
                return this.cacheRoute.map(item=> item.compName)
              },
              key () {
                return this.$route.fullPath
              },
            },
            methods: {
              removeCacheRoute (fullPath) {
                const cache=this.$refs.routerViewRef.$vnode.parent.componentInstance.cache
                delete cache[fullPath]
              },
            },
          }
          </script>
          
          

          路由守衛 導航守衛主要用來通過跳轉或取消的方式守衛導航。有多種機會植入路由導航過程中:全局的, 單個路由獨享的, 或者組件級的。 通俗來講:路由守衛就是路由跳轉過程中的一些生命周期函數(鉤子函數),我們可以利用這些鉤子函數幫我們實現一些需求。 路由守衛又具體分為 全局路由守衛獨享守衛組件路由守衛。 全局路由守衛

          • 全局前置守衛router.beforeEach
          • 全局解析守衛:router.beforeResolve
          • 全局后置守衛:router.afterEach

          beforeEach(to,from, next) 在路由跳轉前觸發,參數包括to,from,next 三個,這個鉤子作用主要是用于登錄驗證。 前置守衛也可以理解為一個路由攔截器,也就是說所有的路由在跳轉前都要先被前置守衛攔截。

          
          router.beforeEach(async (to, from, next)=> {
            // 清除面包屑導航數據
            store.commit('common/SET_BREAD_NAV', [])
            // 是否白名單
            if (isWhiteList(to)) {
              next()
            } else {
              // 未登錄,先登錄
              try {
                if (!store.state.user.userInfo) {
                  await store.dispatch('user/getUserInfo')
                  // 登錄后判斷,是否有角色, 無角色 到平臺默認頁
                  if (!store.state.user.userInfo.permissions || !store.state.user.userInfo.permissions.length) {
                    next({ path: '/noPermission' })
                  }
                }
          
                // 登錄后判斷,是否有訪問頁面的權限
                if (!hasVisitPermission(to, store.state.user.userInfo)) {
                  next({ path: '/404' })
                } else {
                  next()
                }
              } catch (err) {
                $error(err)
              }
            }
          })

          beforeResolve(to,from, next) 在每次導航時都會觸發,區別是在導航被確認之前,同時在所有組件內守衛和異步路由組件被解析之后,解析守衛就被正確調用。 即在 beforeEach 和 組件內 beforeRouteEnter 之后,afterEach之前調用。 router.beforeResolve 是獲取數據或執行任何其他操作的理想位置

          router.beforeResolve(async to=> {
            if (to.meta.requiresCamera) {
              try {
                await askForCameraPermission()
              } catch (error) {
                if (error instanceof NotAllowedError) {
                  // ... 處理錯誤,然后取消導航
                  return false
                } else {
                  // 意料之外的錯誤,取消導航并把錯誤傳給全局處理器
                  throw error
                }
              }
            }
          })

          afterEach(to,from)

          和beforeEach相反,他是在路由跳轉完成后觸發,參數包括to, from 由于此時路由已經完成跳轉 所以不會再有next。

          全局后置守衛對于分析、更改頁面標題、聲明頁面等輔助功能以及許多其他事情都很有用。

          router.afterEach((to, from)=> {
          	// 在路由完成跳轉后執行,實現分析、更改頁面標題、聲明頁面等輔助功能
          	sendToAnalytics(to.fullPath)
          })

          獨享路由守衛

          beforeEnter(to,from, next) 獨享路由守衛可以直接在路由配置上定義,但是它只在進入路由時觸發,不會在 params、query 或 hash 改變時觸發。

          const routes=[
            {
              path: '/users/:id',
              component: UserDetails,
              // 在路由配置中定義守衛
              beforeEnter: (to, from,next)=> {
                next()
              },
            },
          ]
          
          

          或是使用數組的方式傳遞給 beforeEnter ,有利于實現路由守衛的重用

          function removeQueryParams(to) {
            if (Object.keys(to.query).length)
              return { path: to.path, query: {}, hash: to.hash }
          }
          
          function removeHash(to) {
            if (to.hash) return { path: to.path, query: to.query, hash: '' }
          }
          
          const routes=[
            {
              path: '/users/:id',
              component: UserDetails,
              beforeEnter: [removeQueryParams, removeHash],
            },
            {
              path: '/about',
              component: UserDetails,
              beforeEnter: [removeQueryParams],
            },
          ]

          組件路由守衛 在組件內使用的鉤子函數,類似于組件的生命周期, 鉤子函數執行的順序包括

          • beforeRouteEnter(to,from, next) -- 進入前
          • beforeRouteUpdate(to,from, next) -- 路由變化時
          • beforeRouteLeave(to,from, next) -- 離開后

          組件內路由守衛的執行時機:

          
          <template>
            ...
          </template>
          export default{
            data(){
              //...
            },
            
            // 在渲染該組件的對應路由被驗證前調用
            beforeRouteEnter (to, from, next) {
              // 此時 不能獲取組件實例 this
              // 因為當守衛執行前,組件實例還沒被創建
              next((vm)=>{
                // next 回調 在 組件 beforeMount 之后執行 此時組件實例已創建,
                // 可以通過 vm 訪問組件實例
                console.log('A組件中的路由守衛==>> beforeRouteEnter 中next 回調 vm', vm)
              )
            },
          
            // 可用于檢測路由的變化
            beforeRouteUpdate (to, from, next) {
              // 在當前路由改變,但是該組件被復用時調用  此時組件已掛載完可以訪問組件實例 `this`
              // 舉例來說,對于一個帶有動態參數的路徑 /foo/:id,在 /foo/1 和 /foo/2 之間跳轉的時候,
              // 由于會渲染同樣的 Foo 組件,因此組件實例會被復用。而這個鉤子就會在這個情況下被調用。
              console.log('組件中的路由守衛==>> beforeRouteUpdate')
              next()
            },
          
            // 在導航離開渲染該組件的對應路由時調用
            beforeRouteLeave (to, from, next) {
              // 可以訪問組件實例 `this`
              console.log('A組件中的路由守衛==>> beforeRouteLeave')
              next()
            }
          }
          <style>
          ...
          </style>

          注意 beforeRouteEnter 是支持給 next 傳遞回調的唯一守衛。對于 beforeRouteUpdate 和 beforeRouteLeave 來說,this 已經可用了,所以不支持 傳遞回調,因為沒有必要了

          路由守衛觸發流程

          頁面加載時路由守衛觸發順序:

          添加圖片注釋,不超過 140 字(可選)


          1. 觸發全局的路由守衛 beforeEach
          2. 組件在路由配置的獨享路由 beforeEnter
          3. 進入組件中的 beforeRouteEnter,此時無法獲取組件對象
          4. 觸發全局解析守衛 beforeResolve
          5. 此時路由完成跳轉 觸發全局后置守衛 afterEach
          6. 組件的掛載 beforeCreate --> created --> beforeMount
          7. 路由守衛 beforeRouterEnter 中的 next回調, 此時能夠獲取到組件實例 vm
          8. 完成組件的掛載 mounted

          當點擊切換路由時: A頁面跳轉至B頁面觸發的生命周期及路由守衛順序:

          添加圖片注釋,不超過 140 字(可選)


          1. 導航被觸發進入其他路由。
          2. 在離開的路由組件中調用 beforeRouteLeave 。
          3. 調用全局的前置路由守衛 beforeEach 。
          4. 在重用的組件里調用 beforeRouteUpdate 守衛。
          5. 調用被激活組件的路由配置中調用 beforeEnter。
          6. 解析異步路由組件。
          7. 在被激活的組件中調用 beforeRouteEnter。
          8. 調用全局的 beforeResolve 守衛。
          9. 導航被確認。
          10. 調用全局后置路由 afterEach 鉤子。
          11. 觸發 DOM 更新,激活組件的創建及掛載 beforeCreate (新)-->created (新)-->beforeMount(新) 。
          12. 調用 beforeRouteEnter 守衛中傳給 next 的回調函數,創建好的組件實例會作為回調函數的參數傳入。
          13. 失活組件的銷毀 beforeDestory(舊)-->destoryed(舊)
          14. 激活組件的掛載 mounted(新)

          路由守衛的觸發順序 beforeRouterLeave-->beforeEach-->beforeEnter-->beforeRouteEnter-->beforeResolve-->afterEach--> beforeCreate (新)-->created (新)-->beforeMount(新) -->beforeRouteEnter中的next回調 -->beforeDestory(舊)-->destoryed(舊)-->mounted(新) 當路由更新時:觸發 beforeRouteUpdate 注意: 但凡涉及到有next參數的鉤子,必須調用next() 才能繼續往下執行下一個鉤子,否則路由跳轉等會停止。 vueRouter 實現原理 vueRouter 實現的原理就是 監聽瀏覽器中 url 的 hash值變化,并切換對應的組件 1.路由注冊 通過vue.use()安裝vue-router插件,會執行install方法,并將Vue當做參數傳入install方法 Vue.use(VueRouter)===VueRouter.install() src/install.js

          export function install (Vue) {
            // 確保 install 調用一次
            if (install.installed && _Vue===Vue) return
            install.installed=true
            // 把 Vue 賦值給全局變量
            _Vue=Vue
            const registerInstance=(vm, callVal)=> {
              let i=vm.$options._parentVnode
              if (isDef(i) && isDef(i=i.data) && isDef(i=i.registerRouteInstance)) {
                i(vm, callVal)
              }
            }
            // 為每個組件混入 beforeCreate 鉤子
            // 在 `beforeCreate` 鉤子執行時 會初始化路由
            Vue.mixin({
              beforeCreate () {
                // 判斷組件是否存在 router 對象,該對象只在根組件上有
                if (isDef(this.$options.router)) {
                  // 根路由設置為自己
                  this._routerRoot=this
                  //  this.$options.router就是掛在根組件上的 VueRouter 實例
                  this._router=this.$options.router
                  // 執行VueRouter實例上的init方法,初始化路由
                  this._router.init(this)
                  // 很重要,為 _route 做了響應式處理
                  //   即訪問vm._route時會先向dep收集依賴, 而修改_router 會觸發組件渲染
                  Vue.util.defineReactive(this, '_route', this._router.history.current)
                } else {
                  // 用于 router-view 層級判斷
                  this._routerRoot=(this.$parent && this.$parent._routerRoot) || this
                }
                registerInstance(this, this)
              },
              destroyed () {
                registerInstance(this)
              }
            })
            
            /* 在Vue的prototype上面綁定 $router,
               這樣可以在任意Vue對象中使用this.$router訪問,同時經過Object.defineProperty,將 $router 代理到 Vue
               訪問this.$router 即訪問this._routerRoot._router */
            Object.defineProperty(Vue.prototype, '$router', {
              get () { return this._routerRoot._router }
            })
          
            /* 同理,訪問this.$route即訪問this._routerRoot._route */
            Object.defineProperty(Vue.prototype, '$route', {
              get () { return this._routerRoot._route }
            })
          
            // 全局注冊組件 router-link 和 router-view
            Vue.component('RouterView', View)
            Vue.component('RouterLink', Link)
          }


          1. 使用 Vue.mixin 為每個組件混入 beforeCreate 鉤子,全局混入添加組件選項 掛載 router 配置項
          2. 通過 defineReactive 為vue實例實現數據劫持 讓_router能夠及時響應頁面更新
          3. 將 router、router 、router、route 代理到 Vue 原型上
          4. 全局注冊 router-view 及 router-link 組件

          2. VueRouter 實例化 在安裝插件后,對 VueRouter 進行實例化。

          //用戶定義的路由配置數組
          const Home={ template: '<div>home</div>' }
          const Foo={ template: '<div>foo</div>' }
          const Bar={ template: '<div>bar</div>' }
          
          // 3. Create the router
          const router=new VueRouter({
            mode: 'hash',
            base: __dirname,
            routes: [
              { path: '/', component: Home }, // all paths are defined without the hash.
              { path: '/foo', component: Foo },
              { path: '/bar', component: Bar }
            ]
          })

          VueRouter 構造函數

          src/index.js

          // VueRouter 的構造函數
          constructor(options: RouterOptions={}) {
              // ...
              // 路由匹配對象 -- 路由映射表
              this.matcher=createMatcher(options.routes || [], this)
          
              // 根據 mode 采取不同的路由方式
              let mode=options.mode || 'hash'
              this.fallback=mode==='history' && !supportsPushState && options.fallback !==false
              if (this.fallback) {
                mode='hash'
              }
              if (!inBrowser) {
                mode='abstract'
              }
              this.mode=mode
          
              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}`)
                  }
              }
            }

          在實例化 vueRouter 的過程中 通過 createMatcher 創建路由匹配對象(路由映射表),并且根據 mode 來采取不同的路由方式。

          3.創建路由匹配對象

          src/create-matcher.js

          export function createMatcher (
            routes: Array<RouteConfig>,
            router: VueRouter
          ): Matcher {
              // 創建路由映射表
            const { pathList, pathMap, nameMap }=createRouteMap(routes)
              
            function addRoutes (routes) {
              createRouteMap(routes, pathList, pathMap, nameMap)
            }
            // 路由匹配 找到對應的路由
            function match (
              raw: RawLocation,
              currentRoute?: Route,
              redirectedFrom?: Location
            ): Route {
              //...
            }
          
            return {
              match,
              addRoutes
            }
          }

          createMatcher 函數的作用就是創建路由映射表,然后通過閉包的方式讓 addRoutesmatch函數能夠使用路由映射表的幾個對象,最后返回一個 Matcher 對象。 在createMatcher中通過使用 createRouteMap() 根據用戶配置的路由規則來創建對應的路由映射表,返回對應的 pathList, pathMap, nameMap createRouteMap 構造函數 主要用于創建映射表,根據用戶的路由配置規則創建對應的路由映射表 src/create-route-map.js

          export function createRouteMap (
            routes: Array<RouteConfig>,
            oldPathList?: Array<string>,
            oldPathMap?: Dictionary<RouteRecord>,
            oldNameMap?: Dictionary<RouteRecord>
          ): {
            pathList: Array<string>;
            pathMap: Dictionary<RouteRecord>;
            nameMap: Dictionary<RouteRecord>;
          } {
            // 創建映射表
            const pathList: Array<string>=oldPathList || []
            const pathMap: Dictionary<RouteRecord>=oldPathMap || Object.create(null)
            const nameMap: Dictionary<RouteRecord>=oldNameMap || Object.create(null)
            // 遍歷路由配置,為每個配置添加路由記錄
            routes.forEach(route=> {
              addRouteRecord(pathList, pathMap, nameMap, route)
            })
            // 確保通配符在最后
            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
            const pathToRegexpOptions: PathToRegexpOptions=route.pathToRegexpOptions || {}
            // 格式化 url,替換 / 
            const normalizedPath=normalizePath(
              path,
              parent,
              pathToRegexpOptions.strict
            )
            // 生成記錄對象
            const record: RouteRecord={
              path: normalizedPath,
              regex: compileRouteRegex(normalizedPath, pathToRegexpOptions),
              components: route.components || { default: route.component },
              instances: {},
              name,
              parent,
              matchAs,
              redirect: route.redirect,
              beforeEnter: route.beforeEnter,
              meta: route.meta || {},
              props: route.props==null
                ? {}
                : route.components
                  ? route.props
                  : { default: route.props }
            }
          
            if (route.children) {
              // 遞歸路由配置的 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}" }`
                )
              }
            }
          }

          4.路由初始化 init

          當根組件調用 beforeCreate 鉤子函數時,會執行插件安裝階段注入的 beforeCreate 函數

          beforeCreate () {
            // 在option上面存在router則代表是根組件 
            if (isDef(this.$options.router)) {
              this._routerRoot=this
              this._router=this.$options.router
              // 執行_router實例的 init 方法   在 VueRouter 構造函數中的 init()
              this._router.init(this)
               // 為 vue 實例定義數據劫持   讓 _router 的變化能及時響應頁面的更新
              Vue.util.defineReactive(this, '_route', this._router.history.current)
            } else {
               // 非根組件則直接從父組件中獲取
              this._routerRoot=(this.$parent && this.$parent._routerRoot) || this
            }
            // 通過 registerInstance(this, this)這個方法來實現對router-view的掛載操作:主要用于注冊及銷毀實例
            registerInstance(this, this)
          },

          在根組件中進行掛載,非根組件從父級中獲取,保證全局只有一個 路由實例 初始化時執行,保證頁面再刷新時也會進行渲染

          init() -- vueRouter 構造函數中的路由初始化

          src/index.js

          init(app: any /* Vue component instance */) {
              // 將當前vm實例保存在app中,保存組件實例
              this.apps.push(app)
              // 如果根組件已經有了就返回
              if (this.app) {
                return
              }
              /* this.app保存當前vm實例 */
              this.app=app
              // 賦值路由模式
              const history=this.history
              // 判斷路由模式,以哈希模式為例
              if (history instanceof HTML5History) {
                // 路由跳轉
                history.transitionTo(history.getCurrentLocation())
              } else if (history instanceof HashHistory) {
                // 添加 hashchange 監聽
                const setupHashListener=()=> {
                  history.setupListeners()
                }
                // 路由跳轉
                history.transitionTo(
                  history.getCurrentLocation(),
                  setupHashListener,
                  setupHashListener
                )
              }
              // 該回調會在 transitionTo 中調用
              // 對組件的 _route 屬性進行賦值,觸發組件渲染
              history.listen(route=> {
                this.apps.forEach(app=> {
                  app._route=route
                })
              })
            }

          init() 核心就是進行路由的跳轉,改變 URL 然后渲染對應的組件。 路由初始化:

          1. 在Vue調用init進行初始化時會調用beforeCreate鉤子函數
          2. init方法中調用了transationTo 路由跳轉
          3. 在transationTo方法中又調用了confirmTransation 確認跳轉路由,最終在這里執行了runQueue方法,
          4. runQueue 會把隊列 queue 中的所有函數調用執行,其中就包括 路由守衛鉤子函數 的執行

          5.路由跳轉 transitionTo src/history/base.js

          transitionTo (location: RawLocation, onComplete?: Function, onAbort?: Function) {
            // 獲取匹配的路由信息
            const route=this.router.match(location, this.current)
            // 確認切換路由
            this.confirmTransition(route, ()=> {
              // 以下為切換路由成功或失敗的回調
              // 更新路由信息,對組件的 _route 屬性進行賦值,觸發組件渲染
              // 調用 afterHooks 中的鉤子函數
              this.updateRoute(route)
              // 添加 hashchange 監聽
              onComplete && onComplete(route)
              
              // 更新 URL
              this.ensureURL()
              // 只執行一次 ready 回調
              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) })
              }
            })
          }
          
          
           updateRoute (route: Route) {
              // 更新當前路由信息  對組件的 _route 屬性進行賦值,觸發組件渲染
              const prev=this.current
              this.current=route
              this.cb && this.cb(route)
              // 路由跳轉完成 調用 afterHooks 中的鉤子函數
              this.router.afterHooks.forEach(hook=> {
                hook && hook(route, prev)
              })
            }

          在路由跳轉前要先匹配路由信息,在確認切換路由后更新路由信息,觸發組件的渲染,最后更新 url

          Matcher 中的 match() 在路由配置中匹配到相應的路由則創建對應的路由信息

          src/create-matcher.js

          function match (
            raw: RawLocation,
            currentRoute?: Route,
            redirectedFrom?: Location
          ): Route {
            // 序列化 url
            // 比如對于該 url 來說 /abc?foo=bar&baz=qux#hello
            // 會序列化路徑為 /abc
            // 哈希為 #hello
            // 參數為 foo: 'bar', baz: 'qux'
            const location=normalizeLocation(raw, currentRoute, false, router)
            const { name }=location
            // 如果是命名路由,就判斷記錄中是否有該命名路由配置
            if (name) {
              const record=nameMap[name]
              // 沒找到表示沒有匹配的路由
              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)
                }
              }
            }
            // 沒有匹配的路由 返回空的路由
            return _createRoute(null, location)
          }
          
          
          

          通過matcher的match方法(有name匹配name,沒有就匹配path,然后返回,默認重新生成一條路由返回) 解析用戶的路由配置并按照route類型返回,然后路由切換就按照這個route來。 根據匹配的條件創建路由 _createRoute() src/create-matcher.js

          function _createRoute (
              record: ?RouteRecord,
              location: Location,
              redirectedFrom?: Location
            ): Route {
              // 根據條件創建不同的路由
              if (record && record.redirect) {
                return redirect(record, redirectedFrom || location)
              }
              if (record && record.matchAs) {
                return alias(record, location, record.matchAs)
              }
              return createRoute(record, location, redirectedFrom, router)
            }
          
            return {
              match,
              addRoute,
              getRoutes,
              addRoutes
            }
          }

          createRoute ()

          src/util/route.js

          export function createRoute (
            record: ?RouteRecord,
            location: Location,
            redirectedFrom?: ?Location,
            router?: VueRouter
          ): Route {
            const stringifyQuery=router && router.options.stringifyQuery
          
            let query: any=location.query || {}
            try {
              // 深拷貝
              query=clone(query)
            } catch (e) {}
            // 創建路由對象
            const route: Route={
              name: location.name || (record && record.name),
              meta: (record && record.meta) || {},
              path: location.path || '/',
              hash: location.hash || '',
              query,
              params: location.params || {},
              fullPath: getFullPath(location, stringifyQuery),
              matched: record ? formatMatch(record) : []
            }
            if (redirectedFrom) {
              route.redirectedFrom=getFullPath(redirectedFrom, stringifyQuery)
            }
            // 通過Object.freeze定義的只讀對象 route
            return Object.freeze(route)
          }
          
          
          // 獲得包含當前路由的所有嵌套路徑片段的路由記錄
          // 包含從根路由到當前路由的匹配記錄,從上至下
          function formatMatch (record: ?RouteRecord): Array<RouteRecord> {
            const res=[]
            while (record) {
              res.unshift(record)
              record=record.parent
            }
            return res
          }

          6. 確認跳轉

          至此匹配路由已經完成,我們回到 transitionTo 函數中,接下來執行 confirmTransition

          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()
            }
            // 通過對比路由解析出可復用的組件,需要渲染的組件,失活的組件
            const { updated, deactivated, activated }=resolveQueue(
              this.current.matched,
              route.matched
            )
            
            function resolveQueue(
                current: Array<RouteRecord>,
                next: Array<RouteRecord>
              ): {
                updated: Array<RouteRecord>,
                activated: Array<RouteRecord>,
                deactivated: Array<RouteRecord>
              } {
                let i
                const max=Math.max(current.length, next.length)
                for (i=0; i < max; i++) {
                  // 當前路由路徑和跳轉路由路徑不同時跳出遍歷
                  if (current[i] !==next[i]) {
                    break
                  }
                }
                return {
                  // 可復用的組件對應路由
                  updated: next.slice(0, i),
                  // 需要渲染的組件對應路由
                  activated: next.slice(i),
                  // 失活的組件對應路由
                  deactivated: current.slice(i)
                }
            }
            // 導航守衛數組
            const queue: Array<?NavigationGuard>=[].concat(
              // 失活的組件鉤子
              extractLeaveGuards(deactivated),
              // 全局 beforeEach 鉤子
              this.router.beforeHooks,
              // 在當前路由改變,但是該組件被復用時調用
              extractUpdateHooks(updated),
              // 需要渲染組件 enter 守衛鉤子
              activated.map(m=> m.beforeEnter),
              // 解析異步路由組件
              resolveAsyncComponents(activated)
            )
            // 保存路由
            this.pending=route
            // 迭代器,用于執行 queue 中的導航守衛鉤子
            const iterator=(hook: NavigationGuard, next)=> {
            // 路由不相等就不跳轉路由
              if (this.pending !==route) {
                return abort()
              }
              try {
              // 執行鉤子
                hook(route, current, (to: any)=> {
                  // 只有執行了鉤子函數中的 next,才會繼續執行下一個鉤子函數
                  // 否則會暫停跳轉
                  // 以下邏輯是在判斷 next() 中的傳參
                  if (to===false || isError(to)) {
                    // next(false) 
                    this.ensureURL(true)
                    abort(to)
                  } else if (
                    typeof to==='string' ||
                    (typeof to==='object' &&
                      (typeof to.path==='string' || typeof to.name==='string'))
                  ) {
                  // next('/') 或者 next({ path: '/' }) -> 重定向
                    abort()
                    if (typeof to==='object' && to.replace) {
                      this.replace(to)
                    } else {
                      this.push(to)
                    }
                  } else {
                  // 這里執行 next
                  // 通過 runQueue 中的 step(index+1) 執行 next()
                    next(to)
                  }
                })
              } catch (e) {
                abort(e)
              }
            }
            // 經典的同步執行異步函數
            runQueue(queue, iterator, ()=> {
              const postEnterCbs=[]
              const isValid=()=> this.current===route
              // 當所有異步組件加載完成后,會執行這里的回調,也就是 runQueue 中的 cb()
              // 接下來執行 需要渲染組件中的 beforeRouteEnter 導航守衛鉤子
              const enterGuards=extractEnterGuards(activated, postEnterCbs, isValid)
              // beforeResolve 解析路由鉤子
              const queue=enterGuards.concat(this.router.resolveHooks)
              runQueue(queue, iterator, ()=> {
              // 跳轉完成
                if (this.pending !==route) {
                  return abort()
                }
                this.pending=null
                onComplete(route)
                if (this.router.app) {
                  this.router.app.$nextTick(()=> {
                    postEnterCbs.forEach(cb=> {
                      cb()
                    })
                  })
                }
              })
            })
          }
          export function runQueue (queue: Array<?NavigationGuard>, fn: Function, cb: Function) {
            const step=index=> {
            // 隊列中的函數都執行完畢,就執行回調函數
              if (index >=queue.length) {
                cb()
              } else {
                if (queue[index]) {
                // 執行迭代器,用戶在鉤子函數中執行 next() 回調
                // 回調中判斷傳參,沒有問題就執行 next(),也就是 fn 函數中的第二個參數
                  fn(queue[index], ()=> {
                    step(index + 1)
                  })
                } else {
                  step(index + 1)
                }
              }
            }
            // 取出隊列中第一個鉤子函數
            step(0)
          }
          
          

          7. 導航守衛

          導航守衛在 確認路由跳轉中出現

          const queue: Array<?NavigationGuard>=[].concat(
              // 失活的組件鉤子
            	/*
               *  找出組件中對應的鉤子函數, 給每個鉤子函數添加上下文對象為組件自身
               *  數組降維,并且判斷是否需要翻轉數組,因為某些鉤子函數需要從子執行到父,
               *  獲得鉤子函數數組
               */ 
              extractLeaveGuards(deactivated),
              // 全局 beforeEach 鉤子, 將函數 push 進 beforeHooks 中。
              this.router.beforeHooks,
              // 在當前路由改變,但是該組件被復用時調用
              extractUpdateHooks(updated),
              // 需要渲染組件 beforeEnter 守衛鉤子
              activated.map(m=> m.beforeEnter),
              // 解析異步路由組件
              resolveAsyncComponents(activated)
          )
          
          

          先執行失活組件 deactivated 的鉤子函數 ,找出對應組件中的鉤子函數

          
          function extractLeaveGuards(deactivated: Array<RouteRecord>): Array<?Function> {
          // 傳入需要執行的鉤子函數名  失活組件觸發 beforeRouteLeave 
            return extractGuards(deactivated, 'beforeRouteLeave', bindGuard, true)
          }
          
          function extractUpdateHooks (updated: Array<RouteRecord>): Array<?Function> {
            return extractGuards(updated, 'beforeRouteUpdate', bindGuard)
          }
          
          
          function extractGuards(
            records: Array<RouteRecord>,
            name: string,
            bind: Function,
            reverse?: boolean
          ): Array<?Function> {
            const guards=flatMapComponents(records, (def, instance, match, key)=> {
             // 找出組件中對應的鉤子函數
              const guard=extractGuard(def, name)
              if (guard) {
              // 給每個鉤子函數添加上下文對象為組件自身
                return Array.isArray(guard)
                  ? guard.map(guard=> bind(guard, instance, match, key))
                  : bind(guard, instance, match, key)
              }
            })
            // 數組降維,并且判斷是否需要翻轉數組
            // 因為某些鉤子函數需要從子執行到父
            return flatten(reverse ? guards.reverse() : guards)
          }
          export function flatMapComponents (
            matched: Array<RouteRecord>,
            fn: Function
          ): Array<?Function> {
          // 數組降維
            return flatten(matched.map(m=> {
            // 將組件中的對象傳入回調函數中,獲得鉤子函數數組
              return Object.keys(m.components).map(key=> fn(
                m.components[key],
                m.instances[key],
                m, key
              ))
            }))
          }

          執行全局 beforeEach 鉤子函數, 將函數 push 進 beforeHooks 中。

          beforeEach(fn: Function): Function {
              return registerHook(this.beforeHooks, fn)
          }
          function registerHook(list: Array<any>, fn: Function): Function {
            list.push(fn)
            return ()=> {
              const i=list.indexOf(fn)
              if (i > -1) list.splice(i, 1)
            }
          }
          1. 執行 beforeRouteUpdate 鉤子函數 與 deactivated 實現類似
          2. 執行 beforeEnter 獨享路由鉤子
          3. 解析異步組件
          export function resolveAsyncComponents (matched: Array<RouteRecord>): Function {
            return (to, from, next)=> {
              let hasAsync=false
              let pending=0
              let error=null
              // 扁平化數組 獲取 組件中的鉤子函數數組
              flatMapComponents(matched, (def, _, match, key)=> {
              // 判斷是否是異步組件
                if (typeof def==='function' && def.cid===undefined) {
                  // 異步組件
                  hasAsync=true
                  pending++
                  // 成功回調
                  // once 函數確保異步組件只加載一次
                  const resolve=once(resolvedDef=> {
                    if (isESModule(resolvedDef)) {
                      resolvedDef=resolvedDef.default
                    }
                    // 判斷是否是構造函數
                    // 不是的話通過 Vue 來生成組件構造函數
                    def.resolved=typeof resolvedDef==='function'
                      ? resolvedDef
                      : _Vue.extend(resolvedDef)
                  // 賦值組件
                  // 如果組件全部解析完畢,繼續下一步
                    match.components[key]=resolvedDef
                    pending--
                    if (pending <=0) {
                      next()
                    }
                  })
                  // 失敗回調
                  const reject=once(reason=> {
                    const msg=`Failed to resolve async component ${key}: ${reason}`
                    process.env.NODE_ENV !=='production' && warn(false, msg)
                    if (!error) {
                      error=isError(reason)
                        ? reason
                        : new Error(msg)
                      next(error)
                    }
                  })
                  let res
                  try {
                  // 執行異步組件函數
                    res=def(resolve, reject)
                  } catch (e) {
                    reject(e)
                  }
                  if (res) {
                  // 下載完成執行回調
                    if (typeof res.then==='function') {
                      res.then(resolve, reject)
                    } else {
                      const comp=res.component
                      if (comp && typeof comp.then==='function') {
                        comp.then(resolve, reject)
                      }
                    }
                  }
                }
              })
              // 不是異步組件直接下一步
              if (!hasAsync) next()
            }
          }

          異步組件解析后會執行 runQueue 中的回調函數

            // 經典的同步執行異步函數
            runQueue(queue, iterator, ()=> {
              const postEnterCbs=[] // 存放beforeRouteEnter 中的回調函數
              const isValid=()=> this.current===route
              // 當所有異步組件加載完成后,會執行這里的回調,也就是 runQueue 中的 cb()
              // 接下來執行 需要渲染組件中的 beforeRouteEnter 導航守衛鉤子
              const enterGuards=extractEnterGuards(activated, postEnterCbs, isValid)
              // beforeResolve 導航守衛鉤子
              const queue=enterGuards.concat(this.router.resolveHooks)
              runQueue(queue, iterator, ()=> {
              // 跳轉完成
                if (this.pending !==route) {
                  return abort()
                }
                this.pending=null
                onComplete(route)
                if (this.router.app) {
                  this.router.app.$nextTick(()=> {
                    postEnterCbs.forEach(cb=> {
                      cb()
                    })
                  })
                }
              })
            })
          1. 執行 beforeRouterEnter ,因為在 beforeRouterEnter 在路由確認之前組件還未渲染,所以此時無法訪問到組件的 this 。

          但是該鉤子函數在路由確認執行,是唯一一個支持在 next 回調中獲取 this 對象的函數。

          
          // beforeRouteEnter 鉤子函數
          function extractEnterGuards (
            activated: Array<RouteRecord>,
            cbs: Array<Function>,
            isValid: ()=> boolean
          ): Array<?Function> {
            return extractGuards(activated, 'beforeRouteEnter', (guard, _, match, key)=> {
              return bindEnterGuard(guard, match, key, cbs, isValid)
            })
          }
          
          function bindEnterGuard (
            guard: NavigationGuard,
            match: RouteRecord,
            key: string,
            cbs: Array<Function>,
            isValid: ()=> boolean
          ): NavigationGuard {
            return function routeEnterGuard (to, from, next) {
              return guard(to, from, cb=> {
                next(cb)
                if (typeof cb==='function') {
                  // 判斷 cb 是否是函數
                  // 是的話就 push 進 postEnterCbs
                  cbs.push(()=> {
                    // #750
                    // if a router-view is wrapped with an out-in transition,
                    // the instance may not have been registered at this time.
                    // we will need to poll for registration until current route
                    // is no longer valid.
                     // 循環直到拿到組件實例
                    poll(cb, match.instances, key, isValid)
                  })
                }
              })
            }
          }
          
          // 該函數是為了解決 issus #750
          // 當 router-view 外面包裹了 mode 為 out-in 的 transition 組件 
          // 會在組件初次導航到時獲得不到組件實例對象
          function poll (
            cb: any, // somehow flow cannot infer this is a function
            instances: Object,
            key: string,
            isValid: ()=> boolean
          ) {
            if (instances[key]) {
              cb(instances[key])
            } else if (isValid()) {
              // setTimeout 16ms 作用和 nextTick 基本相同
              setTimeout(()=> {
                poll(cb, instances, key, isValid)
              }, 16)
            }
          }
          1. 執行 beforeResolve 導航守衛鉤子,如果注冊了全局 beforeResolve 鉤子就會在這里執行。
          2. 導航確認完成后 updateRoute 切換路由,更新路由信息后 調用 afterEach 導航守衛鉤子
          updateRoute (route: Route) {
            // 更新當前路由信息  對組件的 _route 屬性進行賦值,觸發組件渲染
            const prev=this.current
            this.current=route
            this.cb && this.cb(route)  // 實際執行 init傳入的回調, app._route=route 對組件的 _route 屬性進行賦值
            // 路由跳轉完成 調用 afterHooks 中的鉤子函數
            this.router.afterHooks.forEach(hook=> {
              hook && hook(route, prev)
            })
          }

          this.cb 是怎么來的呢? 其實 this.cb 是通過 History.listen 實現的,在VueRouter 的初始化 init 過程中對 this.cb 進行了賦值

          //  History 類中 的listen 方法對this.cb 進行賦值
          listen (cb: Function) {
            this.cb=cb
          }
          
          //  init 中執行了 history.listen,將回調函數賦值給 this.cb
          init (app: any /* Vue component instance */) {
            this.apps.push(app)
            history.listen(route=> {
              this.apps.forEach((app)=> {
                app._route=route
              })
            })
          }
          1. 觸發組件的渲染

          當app._router 發生變化時觸發 vue 的響應式調用render() 將路由相應的組件渲染到中

          app._route=route  

          hash 模式的實現

          hash模式的原理是監聽瀏覽器url中hash值的變化,并切換對應的組件

          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()
            }
            // 監聽 hash 的變化
            setupListeners () {
              const router=this.router
              const expectScroll=router.options.scrollBehavior
              const supportsScroll=supportsPushState && expectScroll
              
              if (supportsScroll) {
                setupScroll()
              }
              
              window.addEventListener(supportsPushState ? 'popstate' : 'hashchange', ()=> {
                const current=this.current
                if (!ensureSlash()) {
                  return
                }
                // 傳入當前的 hash 并觸發跳轉
                this.transitionTo(getHash(), route=> {
                  if (supportsScroll) {
                    handleScroll(this.router, route, current, true)
                  }
                  if (!supportsPushState) {
                    replaceHash(route.fullPath)
                  }
                })
              })
            }   
          }
          
          // 如果瀏覽器沒有 # 則自動補充 /#/
          function ensureSlash (): boolean {
            const path=getHash()
            if (path.charAt(0)==='/') {
              return true
            }
            replaceHash('/' + path)
            return false
          }
          
          
          export default HashHistory

          如果手動刷新頁面的話,是不會觸發hashchange事件的,也就是找不出組件來,那咋辦呢?刷新頁面肯定會使路由重新初始化,咱們只需要在初始化函數init 上執行一次原地跳轉就行。 router-view 組件渲染 組件渲染的關鍵在于 router-view ,將路由變化時匹配到的組件進行渲染。 routerView是一個函數式組件,函數式組件沒有data,沒有組件實例。 因此使用了父組件中的$createElement函數,用以渲染組件,并且在組件渲染的各個時期注冊了hook 如果被 keep-alive 包裹則直接使用緩存的 vnode 通過 depth 實現路由嵌套, 循環向上級訪問,直到訪問到根組件,得到路由的 depth 深度

          export default {
            name: 'RouterView',
            /* 
              https://cn.vuejs.org/v2/api/#functional
              使組件無狀態 (沒有 data ) 和無實例 (沒有 this 上下文)。他們用一個簡單的 render 函數返回虛擬節點使他們更容易渲染。
            */
            functional: true,
            props: {
              name: {
                type: String,
                default: 'default'
              }
            },
            render (_, { props, children, parent, data }) {
              /* 標記位,標記是route-view組件 */
              data.routerView=true
          
              /* 直接使用父組件的createElement函數  因此router-view渲染的組件可以解析命名槽*/
              const h=parent.$createElement
              /* props的name,默認'default' */
              const name=props.name
              /* option中的VueRouter對象 */
              const route=parent.$route
              /* 在parent上建立一個緩存對象 */
              const cache=parent._routerViewCache || (parent._routerViewCache={})
          
          
              /* 記錄組件深度 用于實現路由嵌套 */
              let depth=0
              /* 標記是否是待用(非alive狀態)) */
              let inactive=false
              /* _routerRoot中中存放了根組件的勢力,這邊循環向上級訪問,直到訪問到根組件,得到depth深度 */
              // 用 depth 幫助找到對應的 RouterRecord
              while (parent && parent._routerRoot !==parent) {
                if (parent.$vnode && parent.$vnode.data.routerView) {
                  // 遇到其他的 router-view 組件則路由深度+1 
                  depth++
                }
                /* 如果_inactive為true,代表是在keep-alive中且是待用(非alive狀態) */
                if (parent._inactive) {
                  inactive=true
                }
                parent=parent.$parent
              }
              /* 存放route-view組件的深度 */
              data.routerViewDepth=depth
          
              /* 如果inactive為true說明在keep-alive組件中,直接從緩存中取 */
              if (inactive) {
                return h(cache[name], data, children)
              }
          
              // depth 幫助 route.matched 找到對應的路由記錄
              const matched=route.matched[depth]
          
              /* 如果沒有匹配到的路由,則渲染一個空節點 */
              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
              /* 注冊實例的registration鉤子,這個函數將在實例被注入的加入到組件的生命鉤子(beforeCreate與destroyed)中被調用 */
              data.registerRouteInstance=(vm, val)=> {  
                /* 第二個值不存在的時候為注銷 */
                // val could be undefined for unregistration
                /* 獲取組件實例 */
                const current=matched.instances[name]
                if (
                  (val && current !==vm) ||
                  (!val && current===vm)
                ) {
                  /* 這里有兩種情況,一種是val存在,則用val替換當前組件實例,另一種則是val不存在,則直接將val(這個時候其實是一個undefined)賦給instances */
                  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
              let propsToPass=data.props=resolveProps(route, matched.props && matched.props[name])
              if (propsToPass) {
                // clone to prevent mutation
                propsToPass=data.props=extend({}, propsToPass)
                // pass non-declared props as attrs
                const attrs=data.attrs=data.attrs || {}
                for (const key in propsToPass) {
                  if (!component.props || !(key in component.props)) {
                    attrs[key]=propsToPass[key]
                    delete propsToPass[key]
                  }
                }
              }
          
              return h(component, data, children)
            }
          }
          
          

          嵌套路由的實現 routerView的render函數通過定義一個depth參數,來判斷當前嵌套的路由是位于matched函數層級,然后取出對應的record對象,渲染器對應的組件。 router-link 組件 router-link 的本質是 a 標簽,在標簽上綁定了click事件,然后執行對應的VueRouter實例的push()實現的

          export default {
            name: 'RouterLink',
            props: {
              to: {
                type: toTypes,
                required: true
              },
              tag: {
                type: String,
                default: 'a'
              },
              exact: Boolean,
              append: Boolean,
              replace: Boolean,  // 當點擊時會調用router.replace()而不是router.push(),這樣導航后不會留下history記錄
              activeClass: String,
              exactActiveClass: String,
              event: {
                type: eventTypes,
                default: 'click'   // 默認為 click 事件
              }
            },
            render (h: Function) {
              // 獲取 $router 實例
              const router=this.$router
              // 獲取當前路由對象
              const current=this.$route
          
              // 要跳轉的地址
              const { location, route, href }=router.resolve(this.to, current, this.append)
              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
          
              classes[exactActiveClass]=isSameRoute(current, compareTarget)
              classes[activeClass]=this.exact
                ? classes[exactActiveClass]
                : isIncludedRoute(current, compareTarget)
          
              const handler=e=> {
                //  綁定點擊事件
                //  若設置了 replace 屬性則使用 router.replace 切換路由
                //  否則使用 router.push 更新路由
                if (guardEvent(e)) {
                  if (this.replace) {
                    //  router.replace()  導航后不會留下history記錄
                    router.replace(location)
                  } else {
                    router.push(location)
                  }
                }
              }
          
              const on={ click: guardEvent }  // <router-link> 組件默認都支持的click事件 
              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') {   // 如果是 a 標簽會綁定監聽事件
                data.on=on  // 監聽自身
                data.attrs={ href }
              } else {
                // find the first <a> child and apply listener and href
                const a=findAnchor(this.$slots.default)    // 如果不是 a標簽則會 找到第一個 a 標簽
                if (a) {                                     
                  // in case the <a> is a static node        // 找到第一個 a 標簽
                  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      // 如果沒找到 a 標簽就監聽自身  
                }
              }
          
              //最后調用$createElement去創建該Vnode
              return h(this.tag, data, this.$slots.default)  
            }
          }
          
          // 阻止瀏覽器的默認事件,所有的事件都是通過 VueRouter 內置代碼實現的
          function guardEvent (e) {
            // don't redirect with control keys
            if (e.metaKey || e.altKey || e.ctrlKey || e.shiftKey) return
            // don't redirect when preventDefault called
            if (e.defaultPrevented) return
            // don't redirect on right click
            if (e.button !==undefined && e.button !==0) return
            // don't redirect if `target="_blank"`
            if (e.currentTarget && e.currentTarget.getAttribute) {
              const target=e.currentTarget.getAttribute('target')
              if (/\b_blank\b/i.test(target)) return
            }
            // this may be a Weex event which doesn't have this method
            if (e.preventDefault) {
              e.preventDefault()
            }
            return true
          }

          何時觸發視圖更新

          在混入 beforeCreate 時 對 _route 作了響應式處理,即訪問vm._route時會先向dep收集依賴

          beforeCreate () {
                // 判斷組件是否存在 router 對象,該對象只在根組件上有
                if (isDef(this.$options.router)) {
                  // 根路由設置為自己
                  this._routerRoot=this
                  //  this.$options.router就是掛在根組件上的 VueRouter 實例
                  this._router=this.$options.router
                  // 執行VueRouter實例上的init方法,初始化路由
                  this._router.init(this)
                  // 很重要,為 _route 做了響應式處理
                  //   即訪問vm._route時會先向dep收集依賴, 而修改 _router 會觸發組件渲染
                  Vue.util.defineReactive(this, '_route', this._router.history.current)
                } else {
                  // 用于 router-view 層級判斷
                  this._routerRoot=(this.$parent && this.$parent._routerRoot) || this
                }
                registerInstance(this, this)
              },
          
          //  訪問vm._route時會先向dep收集依賴
            Object.defineProperty(Vue.prototype, '$router', {
              get () { return this._routerRoot._router }
            })

          訪問 $router 時觸發依賴收集

          • 在組件中使用 this.$router
          • router-link 組件內部

          何時觸發 dep.notify 呢? 路由導航實際執行的history.push方法 會觸發 tansitionTo

            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)
            }

          在確認路由后執行回調時會通過 updateRoute 觸發 this.$route 的修改

          updateRoute (route: Route) {
            // 更新當前路由信息  對組件的 _route 屬性進行賦值,觸發組件渲染
            const prev=this.current
            this.current=route
            this.cb && this.cb(route)
            this.router.afterHooks.forEach(hook=> {
              hook && hook(route, prev)
            })
          }

          其中 this.cb 在路由初始化過程中 通過history.listen 保存的

          //  VueRouter 路由初始化時設置的 listen 回調
          history.listen(route=> {
            this.apps.forEach((app)=> {
              //  $router 的更新==>> app._route=route則觸發了set,即觸發dep.notify向watcher派發更新
              app._route=route
            })
          })
          
          
          // history 類中 cb的取值
          listen (cb: Function) {
            this.cb=cb
          }

          當組件重新渲染, vue 通過 router-view 渲染到指定位置 綜上所述 路由觸發組件更新依舊是沿用的vue組件的響應式核心, 在執行transitionTo 前手動觸發依賴收集, 在路由transitionTo 過程中手動觸發更新派發以達到watcher的重新update; 而之所以路由能正確的顯示對應的組件,則得益于路由映射表中保存的路由樹形關系 $router.push 切換路由的過程 vue-router 通過 vue.mixin 方法注入 beforeCreate 鉤子,該混合在 beforeCreate 鉤子中通過 Vue.util.defineReactive() 定義了響應式的 _route 。所謂響應式屬性,即當 _route 值改變時,會自動調用 Vue 實例的 render() 方法,更新視圖。 vm.render()是根據當前的_route 的 path,nam 等屬性,來將路由對應的組件渲染到 router-view 中

          1. $router.push() //顯式調用方法
          2. HashHistory.push() //根據hash模式調用, 設置hash并添加到瀏覽器歷史記錄(window.location.hash=XXX)
          3. History.transitionTo() //==>> const route=this.router.match(location, this.current) 找到當前路由對應的組件
          4. History.confirmTransition() // 確認路由,在確認頁面跳轉后 觸發路由守衛,并執行相應回調
          5. History.updateRoute() //更新路由
          6. {app._route=route} // 路由的更改派發更新 觸發頁面的更新
          7. vm.render() // 在 中進行 render 更新視圖
          8. window.location.hash=route.fullpath (瀏覽器地址欄顯示新的路由的path)

          History.replace() 在 hash 模式下

          replace (location: RawLocation, onComplete?: Function, onAbort?: Function) {
            const { current: fromRoute }=this
            this.transitionTo(location, route=> {
              replaceHash(route.fullPath)
              handleScroll(this.router, route, fromRoute, false)
              onComplete && onComplete(route)
            }, onAbort)
          }
          
          
          function replaceHash (path) {
            if (supportsPushState) {
              replaceState(getUrl(path))
            } else {
              window.location.replace(getUrl(path))
            }
          }
          
          

          通過 window.location.replace 替換當前路由,這樣不會將新路由添加到瀏覽器訪問歷史的棧頂,而是替換掉當前的路由。

          history模式下

          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)
          }

          監聽地址欄

          在地址欄修改 url 時 vueRouter 會發生什么變化

          當路由采用 hash 模式時,監聽了瀏覽器 hashChange 事件,在路由發生變化后調用 replaceHash()

            //  監聽 hash 的變化
            setupListeners () {
              const router=this.router
              const expectScroll=router.options.scrollBehavior
              const supportsScroll=supportsPushState && expectScroll
          
              if (supportsScroll) {
                setupScroll()
              }
              
              window.addEventListener(supportsPushState ? 'popstate' : 'hashchange', ()=> {
                const current=this.current
                if (!ensureSlash()) {
                  return
                }
                // 傳入當前的 hash 并觸發跳轉
                this.transitionTo(getHash(), route=> {
                  if (supportsScroll) {
                    handleScroll(this.router, route, current, true)
                  }
                  if (!supportsPushState) {
                    replaceHash(route.fullPath)
                  }
                })
              })
            }
          
          

          在路由初始化的時候會添加事件 setupHashListener 來監聽 hashchange 或 popstate;當路由變化時,會觸發對應的 push 或 replace 方法,然后調用 transitionTo 方法里面的 updateRoute 方法來更新 _route,從而觸發 router-view 的變化。 所以在瀏覽器地址欄中直接輸入路由相當于代碼調用了replace()方法,將路由替換成輸入的 url。 在 history 模式下的路由監聽是在構造函數中執行的,對 HTML5History 的 popstate 事件進行監聽

          window.addEventListener('popstate', e=> {
            const current=this.current
            const location=getLocation(this.base)
            if (this.current===START && location===initLocation) {
              return
            }
          
            this.transitionTo(location, route=> {
              if (supportsScroll) {
                handleScroll(router, route, current, true)
              }
            })
          })

          小結 頁面渲染 1、Vue.use(Router) 注冊 2、注冊時調用 install 方法混入生命周期,定義 router 和 route 屬性,注冊 router-view 和 router-link 組件 3、生成 router 實例,根據配置數組(傳入的routes)生成路由配置記錄表,根據不同模式生成監控路由變化的History對象 4、生成 vue 實例,將 router 實例掛載到 vue 實例上面,掛載的時候 router 會執行最開始混入的生命周期函數 5、初始化結束,顯示默認頁面 路由點擊更新 1、 router-link 綁定 click 方法,觸發 history.push 或 history.replace ,從而觸發 history.transitionTo 方法 2、ransitionTo 用于處理路由轉換,其中包含了 updateRoute 用于更新 _route 3、在 beforeCreate 中有劫持 _route 的方法,當 _route 變化后,觸發 router-view 的變化 地址變化路由更新 1、HashHistory 和 HTML5History 會分別監控 hashchange 和 popstate 來對路由變化作對用的處理 2、HashHistory 和 HTML5History 捕獲到變化后會對應執行 push 或 replace 方法,從而調用 transitionTo 3、然后更新 _route 觸發 router-view 的變化 路由相關問題 1. vue-router響應路由參數的變化

          • 通過 watch 監聽 route 對象
          // 監聽當前路由發生變化的時候執行
          watch: {
            $route(to, from){
              console.log(to.path)
              // 對路由變化做出響應
            }
          }
          • 組件中的 beforeRouteUpdate 路由守衛

          在組件被復用的情況下,在同一組件中路由動態傳參的變化 如: 動態參數的路徑 /foo/:id,在 /foo/1 和 /foo/2 之間跳轉的時候,

          beforeRouteUpdate(to, from, next){
            // to do somethings
          }

          2. keep-alive 緩存后獲取數據

          • beforeRouteEnter

          在每次組件渲染時執行 beforeRouterEnter

          beforeRouteEnter(to, from, next){
              next(vm=>{
                  console.log(vm)
                  // 每次進入路由執行
                  vm.getData()  // 獲取數據
              })
          },
          • actived

          在 keep-alive 組件被激活時都會執行 actived 鉤子

          服務器端渲染期間 avtived 不被調用 


          activated(){
          	this.getData() // 獲取數據
          },

          總結 當時在寫這篇文的時候就是想著盡量能把各個知識點都串聯上,建立完善的知識體系 這不寫著寫著就成了長文, 一旦開始就無法停下,那就硬著頭皮繼續吧 不過這篇長文真的是有夠長的,哈哈哈哈,能堅持看到這里的同學我都感到佩服 如果覺得還有哪里缺失的點可以及時告訴我哦 那么今天就先到這啦


          主站蜘蛛池模板: 久久精品道一区二区三区| 亚洲AV无码一区二区三区网址| 国产成人一区二区三区免费视频 | 日韩AV无码一区二区三区不卡毛片 | 亚拍精品一区二区三区| 色一乱一伦一区一直爽| 少妇精品无码一区二区三区| 制服美女视频一区| 中文字幕一区二区精品区 | 亚洲熟女综合一区二区三区| 国产熟女一区二区三区四区五区 | 91在线一区二区| 国产一区二区免费视频| 亚洲AV成人精品日韩一区18p| 在线免费一区二区| 日本一区二区三区爆乳| 精品国产一区二区三区麻豆| 中文字幕人妻丝袜乱一区三区 | 国产伦精品一区二区三区免费下载 | 好湿好大硬得深一点动态图91精品福利一区二区 | 国产精品亚洲综合一区| aⅴ一区二区三区无卡无码| 一区免费在线观看| 久久亚洲中文字幕精品一区| 一区二区国产在线观看| 日本在线观看一区二区三区| 国产一区韩国女主播| 日本精品一区二区三区在线视频| 日本精品3d动漫一区二区| 国产免费私拍一区二区三区| 国产日韩精品一区二区在线观看| 乱码人妻一区二区三区| 国产日韩综合一区二区性色AV| 国产日韩视频一区| 精品人妻一区二区三区毛片| 中文字幕一区二区三区乱码| 国产精品毛片一区二区三区| 日韩人妻精品无码一区二区三区| 亚洲国产成人精品无码一区二区| 日本一区二区三区爆乳| 色精品一区二区三区|