整合營銷服務(wù)商

          電腦端+手機端+微信端=數(shù)據(jù)同步管理

          免費咨詢熱線:

          VS Code常用插件

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

            VS vode特點:

          開源,免費;

          自定義配置

          集成git

          智能提示強大

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

          調(diào)試功能強大

          各種方便的快捷鍵

          強大的插件擴展

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


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

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

          二.怎么安裝插件?

          方法一:

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

          方法二:

          ctrl + P 然后輸入 >ext install

          方法三:

          點擊圖中位置

          三.插件合集

          插件官網(wǎng):https://marketplace.visualstudio.com/

            每一個插件名都超鏈接到官網(wǎng),注意查看

          a.配置類插件:

          1.Settings Sync

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

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

          2.Debugger for Chrome

          從VS Code調(diào)試在Google Chrome中運行的JavaScript代碼。

          用于在Google Chrome瀏覽器或支持Chrome DevTools協(xié)議的其他目標中調(diào)試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

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


          6.Code Spell Checker

          代碼拼寫檢查器

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

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

          7.vscode-icons

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

          8.guides

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

          9.Rainbow Brackets

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

          效果如下:

          10.Bracket Pair Colorizer

          用于著色匹配括號

          11.Indent-Rainbow

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

          12.filesize

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

          13.Import Cost

          對引入的計算大小


          14.Path Intellisense

          可自動填充文件名。


          15.WakaTime

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

          16.GitLens

          git日志查看插件

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

          17..REST Client

          REST客戶端允許您直接發(fā)送HTTP請求并在Visual Studio Code中查看響應(yīng)。

          18.Npm Intellisense

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

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


          19.Azure Storage

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

          20.Project Manager

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

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

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

          將任何項目保存為收藏夾

          自動檢測VSCode,GIT中,水銀或SVN存放區(qū)

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

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

          一個狀態(tài)欄標識當前項目

          專門的活動欄

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

          這個插件,這個下載次數(shù),安裝就對了。


          22.Todo Tree

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

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

          b.VS code 主題集合

          1.Night Owl

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


          2.Atom One Dark Theme

          一個基于Atom的黑暗主題

          3.Dracula Official

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

          4.One Dark Pro

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

          5.Bimbo

          簡約而現(xiàn)代的神奇海洋主題


          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.語言相關(guān)

          1.C#

          適用于.NET Core的輕量級開發(fā)工具。

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

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

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

          2.CodeMetrics

          計算TypeScript / JavaScript文件的復(fù)雜性。

          3.VUE插件

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

            VueHelper   snippet代碼片段

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

            prettier 代碼規(guī)范性插件

          4. Java Extension Pack

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

          作者:殺死哪個崇明島人

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

          出處: bilibili

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

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

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

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

          上面代碼中 splitView.setRightView 函數(shù)意為設(shè)置右側(cè)組件,有了這個函數(shù),我就可以動態(tài)地改變 spliteView 組件中的右側(cè)組件了。

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

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


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

          history模式 (對應(yīng)HTML5History)

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

          history.pushState 修改瀏覽器地址,而頁面的加載是通過 onpopstate 事件監(jiān)聽實現(xiàn),加載對應(yīng)的頁面內(nèi)容,完成頁面更新。

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

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

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

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

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

          路由組件

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

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

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

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

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

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

          • 把不同路由對應(yīng)的組件分割成不同的代碼塊,當路由被訪問時才去加載對應(yīng)的組件 即為路由的懶加載,可以加快項目的加載速度,提高效率
          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>
            <!-- 展示路由的內(nèi)容 -->
            <router-view></router-view>
          </div>
          
          

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

          import Vue from 'vue'
          import VueRouter from 'vue-router'
          import { hasVisitPermission, isWhiteList } from './permission'
          
          // 注冊路由組件
          Vue.use(VueRouter)
          
          // 創(chuàng)建路由: 每一個路由規(guī)則都是一個對象
          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
          })
          
          // 路由守衛(wèi)
          router.beforeEach(async (to, from, next)=> {
            // 清除面包屑導(dǎo)航數(shù)據(jù)
            store.commit('common/SET_BREAD_NAV', [])
            // 是否白名單
            if (isWhiteList(to)) {
              next()
            } else {
              // 未登錄,先登錄
              try {
                if (!store.state.user.userInfo) {
                  await store.dispatch('user/getUserInfo')
                }
          
                // 登錄后判斷,是否有訪問頁面的權(quán)限
                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')
          
          

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

          • 動態(tài)路由的創(chuàng)建,主要是使用 path 屬性過程中,使用動態(tài)路徑參數(shù),路徑參數(shù) 用冒號 : 表示。

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

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

          _vue-router _通過配置 _params __query _來實現(xiàn)動態(tài)路由

          params 傳參

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

          query 傳參

          • 可以用 name 也可以使用 path 傳參
          • 傳遞的參數(shù)會顯示在 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
          	}
          })
          
          // 接收參數(shù)
          const q=this.$route.query
          
          

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

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

          在不緩存組件實例的情況下,每次切換都會重新 render,執(zhí)行整個生命周期,每次切換時,重新 render,重新請求,必然不滿足需求。 會消耗大量的性能 keep-alive 的基本使用 只是在進入當前路由的第一次render,來回切換不會重新執(zhí)行生命周期,且能緩存router-view的數(shù)據(jù)。 通過 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>
          
          <!-- 數(shù)組 (使用 `v-bind`) -->
          <keep-alive :include="['a', 'b']">
            <component :is="view"></component>
          </keep-alive>

          路由配置 keepAlive

          在路由中設(shè)置 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 對生命周期的影響 設(shè)置緩存后組件加載的生命周期會新增 actived 與 deactived

          • 首次進入組件時也會觸發(fā) actived 鉤子函數(shù):beforeRouteEnter > beforeCreate > created> beforeMount > beforeRouteEnter 的 next 回調(diào)> mounted > activated > ... ... > beforeRouteLeave > deactivated
          • 再次進入組件時直接獲取actived的組件內(nèi)容:beforeRouteEnter >activated > ... ... > beforeRouteLeave > deactivated

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

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

          動態(tài)路由緩存的的具體表現(xiàn)在:

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

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

          在組件內(nèi)可以通過 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>
          
          

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

          • 全局前置守衛(wèi)router.beforeEach
          • 全局解析守衛(wèi):router.beforeResolve
          • 全局后置守衛(wèi):router.afterEach

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

          
          router.beforeEach(async (to, from, next)=> {
            // 清除面包屑導(dǎo)航數(shù)據(jù)
            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' })
                  }
                }
          
                // 登錄后判斷,是否有訪問頁面的權(quán)限
                if (!hasVisitPermission(to, store.state.user.userInfo)) {
                  next({ path: '/404' })
                } else {
                  next()
                }
              } catch (err) {
                $error(err)
              }
            }
          })

          beforeResolve(to,from, next) 在每次導(dǎo)航時都會觸發(fā),區(qū)別是在導(dǎo)航被確認之前,同時在所有組件內(nèi)守衛(wèi)和異步路由組件被解析之后,解析守衛(wèi)就被正確調(diào)用。 即在 beforeEach 和 組件內(nèi) beforeRouteEnter 之后,afterEach之前調(diào)用。 router.beforeResolve 是獲取數(shù)據(jù)或執(zhí)行任何其他操作的理想位置

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

          afterEach(to,from)

          和beforeEach相反,他是在路由跳轉(zhuǎn)完成后觸發(fā),參數(shù)包括to, from 由于此時路由已經(jīng)完成跳轉(zhuǎn) 所以不會再有next。

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

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

          獨享路由守衛(wèi)

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

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

          或是使用數(shù)組的方式傳遞給 beforeEnter ,有利于實現(xiàn)路由守衛(wèi)的重用

          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],
            },
          ]

          組件路由守衛(wèi) 在組件內(nèi)使用的鉤子函數(shù),類似于組件的生命周期, 鉤子函數(shù)執(zhí)行的順序包括

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

          組件內(nèi)路由守衛(wèi)的執(zhí)行時機:

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

          注意 beforeRouteEnter 是支持給 next 傳遞回調(diào)的唯一守衛(wèi)。對于 beforeRouteUpdate 和 beforeRouteLeave 來說,this 已經(jīng)可用了,所以不支持 傳遞回調(diào),因為沒有必要了

          路由守衛(wèi)觸發(fā)流程

          頁面加載時路由守衛(wèi)觸發(fā)順序:

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


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

          當點擊切換路由時: A頁面跳轉(zhuǎn)至B頁面觸發(fā)的生命周期及路由守衛(wèi)順序:

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


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

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

          export function install (Vue) {
            // 確保 install 調(diào)用一次
            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` 鉤子執(zhí)行時 會初始化路由
            Vue.mixin({
              beforeCreate () {
                // 判斷組件是否存在 router 對象,該對象只在根組件上有
                if (isDef(this.$options.router)) {
                  // 根路由設(shè)置為自己
                  this._routerRoot=this
                  //  this.$options.router就是掛在根組件上的 VueRouter 實例
                  this._router=this.$options.router
                  // 執(zhí)行VueRouter實例上的init方法,初始化路由
                  this._router.init(this)
                  // 很重要,為 _route 做了響應(yīng)式處理
                  //   即訪問vm._route時會先向dep收集依賴, 而修改_router 會觸發(fā)組件渲染
                  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訪問,同時經(jīng)過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實例實現(xiàn)數(shù)據(jù)劫持 讓_router能夠及時響應(yīng)頁面更新
          3. 將 router、router 、router、route 代理到 Vue 原型上
          4. 全局注冊 router-view 及 router-link 組件

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

          //用戶定義的路由配置數(shù)組
          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 構(gòu)造函數(shù)

          src/index.js

          // VueRouter 的構(gòu)造函數(shù)
          constructor(options: RouterOptions={}) {
              // ...
              // 路由匹配對象 -- 路由映射表
              this.matcher=createMatcher(options.routes || [], this)
          
              // 根據(jù) 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 創(chuàng)建路由匹配對象(路由映射表),并且根據(jù) mode 來采取不同的路由方式。

          3.創(chuàng)建路由匹配對象

          src/create-matcher.js

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

          createMatcher 函數(shù)的作用就是創(chuàng)建路由映射表,然后通過閉包的方式讓 addRoutesmatch函數(shù)能夠使用路由映射表的幾個對象,最后返回一個 Matcher 對象。 在createMatcher中通過使用 createRouteMap() 根據(jù)用戶配置的路由規(guī)則來創(chuàng)建對應(yīng)的路由映射表,返回對應(yīng)的 pathList, pathMap, nameMap createRouteMap 構(gòu)造函數(shù) 主要用于創(chuàng)建映射表,根據(jù)用戶的路由配置規(guī)則創(chuàng)建對應(yīng)的路由映射表 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>;
          } {
            // 創(chuàng)建映射表
            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

          當根組件調(diào)用 beforeCreate 鉤子函數(shù)時,會執(zhí)行插件安裝階段注入的 beforeCreate 函數(shù)

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

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

          init() -- vueRouter 構(gòu)造函數(shù)中的路由初始化

          src/index.js

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

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

          1. 在Vue調(diào)用init進行初始化時會調(diào)用beforeCreate鉤子函數(shù)
          2. init方法中調(diào)用了transationTo 路由跳轉(zhuǎn)
          3. 在transationTo方法中又調(diào)用了confirmTransation 確認跳轉(zhuǎn)路由,最終在這里執(zhí)行了runQueue方法,
          4. runQueue 會把隊列 queue 中的所有函數(shù)調(diào)用執(zhí)行,其中就包括 路由守衛(wèi)鉤子函數(shù) 的執(zhí)行

          5.路由跳轉(zhuǎn) transitionTo src/history/base.js

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

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

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

          src/create-matcher.js

          function match (
            raw: RawLocation,
            currentRoute?: Route,
            redirectedFrom?: Location
          ): Route {
            // 序列化 url
            // 比如對于該 url 來說 /abc?foo=bar&baz=qux#hello
            // 會序列化路徑為 /abc
            // 哈希為 #hello
            // 參數(shù)為 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)
              // 參數(shù)處理
              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]
                // 如果匹配路由,則創(chuàng)建路由
                if (matchRoute(record.regex, location.path, location.params)) {
                  return _createRoute(record, location, redirectedFrom)
                }
              }
            }
            // 沒有匹配的路由 返回空的路由
            return _createRoute(null, location)
          }
          
          
          

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

          function _createRoute (
              record: ?RouteRecord,
              location: Location,
              redirectedFrom?: Location
            ): Route {
              // 根據(jù)條件創(chuàng)建不同的路由
              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) {}
            // 創(chuàng)建路由對象
            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. 確認跳轉(zhuǎn)

          至此匹配路由已經(jīng)完成,我們回到 transitionTo 函數(shù)中,接下來執(zhí)行 confirmTransition

          confirmTransition(route: Route, onComplete: Function, onAbort?: Function) {
            const current=this.current
            // 中斷跳轉(zhuǎn)路由函數(shù)
            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)
            }
            // 如果是相同的路由就不跳轉(zhuǎn)
            if (
              isSameRoute(route, current) &&
              route.matched.length===current.matched.length
            ) {
              this.ensureURL()
              return abort()
            }
            // 通過對比路由解析出可復(fù)用的組件,需要渲染的組件,失活的組件
            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++) {
                  // 當前路由路徑和跳轉(zhuǎn)路由路徑不同時跳出遍歷
                  if (current[i] !==next[i]) {
                    break
                  }
                }
                return {
                  // 可復(fù)用的組件對應(yīng)路由
                  updated: next.slice(0, i),
                  // 需要渲染的組件對應(yīng)路由
                  activated: next.slice(i),
                  // 失活的組件對應(yīng)路由
                  deactivated: current.slice(i)
                }
            }
            // 導(dǎo)航守衛(wèi)數(shù)組
            const queue: Array<?NavigationGuard>=[].concat(
              // 失活的組件鉤子
              extractLeaveGuards(deactivated),
              // 全局 beforeEach 鉤子
              this.router.beforeHooks,
              // 在當前路由改變,但是該組件被復(fù)用時調(diào)用
              extractUpdateHooks(updated),
              // 需要渲染組件 enter 守衛(wèi)鉤子
              activated.map(m=> m.beforeEnter),
              // 解析異步路由組件
              resolveAsyncComponents(activated)
            )
            // 保存路由
            this.pending=route
            // 迭代器,用于執(zhí)行 queue 中的導(dǎo)航守衛(wèi)鉤子
            const iterator=(hook: NavigationGuard, next)=> {
            // 路由不相等就不跳轉(zhuǎn)路由
              if (this.pending !==route) {
                return abort()
              }
              try {
              // 執(zhí)行鉤子
                hook(route, current, (to: any)=> {
                  // 只有執(zhí)行了鉤子函數(shù)中的 next,才會繼續(xù)執(zhí)行下一個鉤子函數(shù)
                  // 否則會暫停跳轉(zhuǎn)
                  // 以下邏輯是在判斷 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 {
                  // 這里執(zhí)行 next
                  // 通過 runQueue 中的 step(index+1) 執(zhí)行 next()
                    next(to)
                  }
                })
              } catch (e) {
                abort(e)
              }
            }
            // 經(jīng)典的同步執(zhí)行異步函數(shù)
            runQueue(queue, iterator, ()=> {
              const postEnterCbs=[]
              const isValid=()=> this.current===route
              // 當所有異步組件加載完成后,會執(zhí)行這里的回調(diào),也就是 runQueue 中的 cb()
              // 接下來執(zhí)行 需要渲染組件中的 beforeRouteEnter 導(dǎo)航守衛(wèi)鉤子
              const enterGuards=extractEnterGuards(activated, postEnterCbs, isValid)
              // beforeResolve 解析路由鉤子
              const queue=enterGuards.concat(this.router.resolveHooks)
              runQueue(queue, iterator, ()=> {
              // 跳轉(zhuǎn)完成
                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=> {
            // 隊列中的函數(shù)都執(zhí)行完畢,就執(zhí)行回調(diào)函數(shù)
              if (index >=queue.length) {
                cb()
              } else {
                if (queue[index]) {
                // 執(zhí)行迭代器,用戶在鉤子函數(shù)中執(zhí)行 next() 回調(diào)
                // 回調(diào)中判斷傳參,沒有問題就執(zhí)行 next(),也就是 fn 函數(shù)中的第二個參數(shù)
                  fn(queue[index], ()=> {
                    step(index + 1)
                  })
                } else {
                  step(index + 1)
                }
              }
            }
            // 取出隊列中第一個鉤子函數(shù)
            step(0)
          }
          
          

          7. 導(dǎo)航守衛(wèi)

          導(dǎo)航守衛(wèi)在 確認路由跳轉(zhuǎn)中出現(xiàn)

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

          先執(zhí)行失活組件 deactivated 的鉤子函數(shù) ,找出對應(yīng)組件中的鉤子函數(shù)

          
          function extractLeaveGuards(deactivated: Array<RouteRecord>): Array<?Function> {
          // 傳入需要執(zhí)行的鉤子函數(shù)名  失活組件觸發(fā) 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)=> {
             // 找出組件中對應(yīng)的鉤子函數(shù)
              const guard=extractGuard(def, name)
              if (guard) {
              // 給每個鉤子函數(shù)添加上下文對象為組件自身
                return Array.isArray(guard)
                  ? guard.map(guard=> bind(guard, instance, match, key))
                  : bind(guard, instance, match, key)
              }
            })
            // 數(shù)組降維,并且判斷是否需要翻轉(zhuǎn)數(shù)組
            // 因為某些鉤子函數(shù)需要從子執(zhí)行到父
            return flatten(reverse ? guards.reverse() : guards)
          }
          export function flatMapComponents (
            matched: Array<RouteRecord>,
            fn: Function
          ): Array<?Function> {
          // 數(shù)組降維
            return flatten(matched.map(m=> {
            // 將組件中的對象傳入回調(diào)函數(shù)中,獲得鉤子函數(shù)數(shù)組
              return Object.keys(m.components).map(key=> fn(
                m.components[key],
                m.instances[key],
                m, key
              ))
            }))
          }

          執(zhí)行全局 beforeEach 鉤子函數(shù), 將函數(shù) 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. 執(zhí)行 beforeRouteUpdate 鉤子函數(shù) 與 deactivated 實現(xiàn)類似
          2. 執(zhí)行 beforeEnter 獨享路由鉤子
          3. 解析異步組件
          export function resolveAsyncComponents (matched: Array<RouteRecord>): Function {
            return (to, from, next)=> {
              let hasAsync=false
              let pending=0
              let error=null
              // 扁平化數(shù)組 獲取 組件中的鉤子函數(shù)數(shù)組
              flatMapComponents(matched, (def, _, match, key)=> {
              // 判斷是否是異步組件
                if (typeof def==='function' && def.cid===undefined) {
                  // 異步組件
                  hasAsync=true
                  pending++
                  // 成功回調(diào)
                  // once 函數(shù)確保異步組件只加載一次
                  const resolve=once(resolvedDef=> {
                    if (isESModule(resolvedDef)) {
                      resolvedDef=resolvedDef.default
                    }
                    // 判斷是否是構(gòu)造函數(shù)
                    // 不是的話通過 Vue 來生成組件構(gòu)造函數(shù)
                    def.resolved=typeof resolvedDef==='function'
                      ? resolvedDef
                      : _Vue.extend(resolvedDef)
                  // 賦值組件
                  // 如果組件全部解析完畢,繼續(xù)下一步
                    match.components[key]=resolvedDef
                    pending--
                    if (pending <=0) {
                      next()
                    }
                  })
                  // 失敗回調(diào)
                  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 {
                  // 執(zhí)行異步組件函數(shù)
                    res=def(resolve, reject)
                  } catch (e) {
                    reject(e)
                  }
                  if (res) {
                  // 下載完成執(zhí)行回調(diào)
                    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()
            }
          }

          異步組件解析后會執(zhí)行 runQueue 中的回調(diào)函數(shù)

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

          但是該鉤子函數(shù)在路由確認執(zhí)行,是唯一一個支持在 next 回調(diào)中獲取 this 對象的函數(shù)。

          
          // beforeRouteEnter 鉤子函數(shù)
          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 是否是函數(shù)
                  // 是的話就 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.
                     // 循環(huán)直到拿到組件實例
                    poll(cb, match.instances, key, isValid)
                  })
                }
              })
            }
          }
          
          // 該函數(shù)是為了解決 issus #750
          // 當 router-view 外面包裹了 mode 為 out-in 的 transition 組件 
          // 會在組件初次導(dǎo)航到時獲得不到組件實例對象
          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. 執(zhí)行 beforeResolve 導(dǎo)航守衛(wèi)鉤子,如果注冊了全局 beforeResolve 鉤子就會在這里執(zhí)行。
          2. 導(dǎo)航確認完成后 updateRoute 切換路由,更新路由信息后 調(diào)用 afterEach 導(dǎo)航守衛(wèi)鉤子
          updateRoute (route: Route) {
            // 更新當前路由信息  對組件的 _route 屬性進行賦值,觸發(fā)組件渲染
            const prev=this.current
            this.current=route
            this.cb && this.cb(route)  // 實際執(zhí)行 init傳入的回調(diào), app._route=route 對組件的 _route 屬性進行賦值
            // 路由跳轉(zhuǎn)完成 調(diào)用 afterHooks 中的鉤子函數(shù)
            this.router.afterHooks.forEach(hook=> {
              hook && hook(route, prev)
            })
          }

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

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

          當app._router 發(fā)生變化時觸發(fā) vue 的響應(yīng)式調(diào)用render() 將路由相應(yīng)的組件渲染到中

          app._route=route  

          hash 模式的實現(xiàn)

          hash模式的原理是監(jiān)聽瀏覽器url中hash值的變化,并切換對應(yīng)的組件

          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()
            }
            // 監(jiān)聽 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 并觸發(fā)跳轉(zhuǎn)
                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

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

          export default {
            name: 'RouterView',
            /* 
              https://cn.vuejs.org/v2/api/#functional
              使組件無狀態(tài) (沒有 data ) 和無實例 (沒有 this 上下文)。他們用一個簡單的 render 函數(shù)返回虛擬節(jié)點使他們更容易渲染。
            */
            functional: true,
            props: {
              name: {
                type: String,
                default: 'default'
              }
            },
            render (_, { props, children, parent, data }) {
              /* 標記位,標記是route-view組件 */
              data.routerView=true
          
              /* 直接使用父組件的createElement函數(shù)  因此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={})
          
          
              /* 記錄組件深度 用于實現(xiàn)路由嵌套 */
              let depth=0
              /* 標記是否是待用(非alive狀態(tài))) */
              let inactive=false
              /* _routerRoot中中存放了根組件的勢力,這邊循環(huán)向上級訪問,直到訪問到根組件,得到depth深度 */
              // 用 depth 幫助找到對應(yīng)的 RouterRecord
              while (parent && parent._routerRoot !==parent) {
                if (parent.$vnode && parent.$vnode.data.routerView) {
                  // 遇到其他的 router-view 組件則路由深度+1 
                  depth++
                }
                /* 如果_inactive為true,代表是在keep-alive中且是待用(非alive狀態(tài)) */
                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 找到對應(yīng)的路由記錄
              const matched=route.matched[depth]
          
              /* 如果沒有匹配到的路由,則渲染一個空節(jié)點 */
              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鉤子,這個函數(shù)將在實例被注入的加入到組件的生命鉤子(beforeCreate與destroyed)中被調(diào)用 */
              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)
            }
          }
          
          

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

          export default {
            name: 'RouterLink',
            props: {
              to: {
                type: toTypes,
                required: true
              },
              tag: {
                type: String,
                default: 'a'
              },
              exact: Boolean,
              append: Boolean,
              replace: Boolean,  // 當點擊時會調(diào)用router.replace()而不是router.push(),這樣導(dǎo)航后不會留下history記錄
              activeClass: String,
              exactActiveClass: String,
              event: {
                type: eventTypes,
                default: 'click'   // 默認為 click 事件
              }
            },
            render (h: Function) {
              // 獲取 $router 實例
              const router=this.$router
              // 獲取當前路由對象
              const current=this.$route
          
              // 要跳轉(zhuǎn)的地址
              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=> {
                //  綁定點擊事件
                //  若設(shè)置了 replace 屬性則使用 router.replace 切換路由
                //  否則使用 router.push 更新路由
                if (guardEvent(e)) {
                  if (this.replace) {
                    //  router.replace()  導(dǎo)航后不會留下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 標簽會綁定監(jiān)聽事件
                data.on=on  // 監(jiān)聽自身
                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 標簽就監(jiān)聽自身  
                }
              }
          
              //最后調(diào)用$createElement去創(chuàng)建該Vnode
              return h(this.tag, data, this.$slots.default)  
            }
          }
          
          // 阻止瀏覽器的默認事件,所有的事件都是通過 VueRouter 內(nèi)置代碼實現(xiàn)的
          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
          }

          何時觸發(fā)視圖更新

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

          beforeCreate () {
                // 判斷組件是否存在 router 對象,該對象只在根組件上有
                if (isDef(this.$options.router)) {
                  // 根路由設(shè)置為自己
                  this._routerRoot=this
                  //  this.$options.router就是掛在根組件上的 VueRouter 實例
                  this._router=this.$options.router
                  // 執(zhí)行VueRouter實例上的init方法,初始化路由
                  this._router.init(this)
                  // 很重要,為 _route 做了響應(yīng)式處理
                  //   即訪問vm._route時會先向dep收集依賴, 而修改 _router 會觸發(fā)組件渲染
                  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 時觸發(fā)依賴收集

          • 在組件中使用 this.$router
          • router-link 組件內(nèi)部

          何時觸發(fā) dep.notify 呢? 路由導(dǎo)航實際執(zhí)行的history.push方法 會觸發(fā) 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)
            }

          在確認路由后執(zhí)行回調(diào)時會通過 updateRoute 觸發(fā) this.$route 的修改

          updateRoute (route: Route) {
            // 更新當前路由信息  對組件的 _route 屬性進行賦值,觸發(fā)組件渲染
            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 路由初始化時設(shè)置的 listen 回調(diào)
          history.listen(route=> {
            this.apps.forEach((app)=> {
              //  $router 的更新==>> app._route=route則觸發(fā)了set,即觸發(fā)dep.notify向watcher派發(fā)更新
              app._route=route
            })
          })
          
          
          // history 類中 cb的取值
          listen (cb: Function) {
            this.cb=cb
          }

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

          1. $router.push() //顯式調(diào)用方法
          2. HashHistory.push() //根據(jù)hash模式調(diào)用, 設(shè)置hash并添加到瀏覽器歷史記錄(window.location.hash=XXX)
          3. History.transitionTo() //==>> const route=this.router.match(location, this.current) 找到當前路由對應(yīng)的組件
          4. History.confirmTransition() // 確認路由,在確認頁面跳轉(zhuǎn)后 觸發(fā)路由守衛(wèi),并執(zhí)行相應(yīng)回調(diào)
          5. History.updateRoute() //更新路由
          6. {app._route=route} // 路由的更改派發(fā)更新 觸發(fā)頁面的更新
          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)
          }

          監(jiān)聽地址欄

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

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

            //  監(jiān)聽 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 并觸發(fā)跳轉(zhuǎn)
                this.transitionTo(getHash(), route=> {
                  if (supportsScroll) {
                    handleScroll(this.router, route, current, true)
                  }
                  if (!supportsPushState) {
                    replaceHash(route.fullPath)
                  }
                })
              })
            }
          
          

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

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

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

          • 通過 watch 監(jiān)聽 route 對象
          // 監(jiān)聽當前路由發(fā)生變化的時候執(zhí)行
          watch: {
            $route(to, from){
              console.log(to.path)
              // 對路由變化做出響應(yīng)
            }
          }
          • 組件中的 beforeRouteUpdate 路由守衛(wèi)

          在組件被復(fù)用的情況下,在同一組件中路由動態(tài)傳參的變化 如: 動態(tài)參數(shù)的路徑 /foo/:id,在 /foo/1 和 /foo/2 之間跳轉(zhuǎn)的時候,

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

          2. keep-alive 緩存后獲取數(shù)據(jù)

          • beforeRouteEnter

          在每次組件渲染時執(zhí)行 beforeRouterEnter

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

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

          服務(wù)器端渲染期間 avtived 不被調(diào)用 


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

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


          主站蜘蛛池模板: 东京热无码一区二区三区av| 精品国产免费观看一区| 久久久精品人妻一区二区三区| 一区二区三区免费视频播放器| 国产一区二区三区免费观看在线| 精品欧美一区二区在线观看 | 日韩精品一区二区三区四区| 日韩视频一区二区在线观看| 中文字幕在线一区二区在线| 精品国产日产一区二区三区| 日韩一区二区三区视频久久| 国内精品一区二区三区最新| 国产精品一区在线麻豆| 日韩好片一区二区在线看| 国产亚洲一区二区精品| 国产丝袜视频一区二区三区| 精品国产免费一区二区三区| 国产一区二区三区亚洲综合 | 国产探花在线精品一区二区| 人妻少妇精品一区二区三区| 中文字幕一区在线观看| 一区二区三区在线播放| 日本精品一区二区久久久 | 麻豆AV一区二区三区| 日韩精品视频一区二区三区| 日本片免费观看一区二区| 亚洲综合色一区二区三区小说| 无码人妻品一区二区三区精99| 肉色超薄丝袜脚交一区二区| 精品无码日韩一区二区三区不卡| 久久久久无码国产精品一区| 亚洲一区影音先锋色资源| 精品人妻系列无码一区二区三区| 国产大秀视频在线一区二区| 精品久久久久久无码中文字幕一区 | 亚洲丰满熟女一区二区哦| 一区二区亚洲精品精华液| 国产无人区一区二区三区| 国产福利电影一区二区三区| 日本一区二区免费看| 精品少妇ay一区二区三区|