你好,談談你對前端路由的理解

一篇文章,不可能作的面面俱到,所有受衆。但願你們帶着發散思惟去看文章,將文章涉及的知識點,吸取爲己所用。這樣看完一篇文章,纔能有所收穫。javascript

前言

好了不裝了,今天我就化身性感面試官在線問你們一個問題,「談談你對前端路由的理解」。看到這個問題,那回答可多了去了。可是換位思考一下,你問候選人這個問題的時候,你想要獲得什麼答案?以我我的拙見,我但願候選人能從全局解讀這個問題,大體如下三點。html

一、爲何會出現前端路由。前端

二、前端路由解決了什麼問題。java

三、前端路由實現的原理是什麼。web

咱們帶着這三個問題,繼續往下看,閱讀的過程當中若是同窗們有本身的看法,能夠評論區發表本身的見解。若是以爲講的內容讓你有了新的看法,請獻上你寶貴的一讚👍,這將是我繼續寫做的動力。面試

傳統頁面

這裏不糾結叫法,凡是整個項目都是 DOM 直出的頁面,咱們都稱它爲「傳統頁面」(SSR 屬於首屏直出,這裏我不認爲是傳統頁面的範疇)。那麼什麼是 DOM 直出呢?簡單說就是在瀏覽器輸入網址後發起請求,返回來的 HTML 頁面是最終呈現的效果,那就是 DOM 直出。而且每次點擊頁面跳轉,都會從新請求 HTML 資源。耳聽爲虛,眼見爲實。咱們以這個地址爲例,驗證如下上述說法。數組

www.cnblogs.com/han-1034683…瀏覽器

腚眼一看,就能明白上圖在描述什麼。沒錯,博客園就是一個傳統頁面搭建而成的網站,每次加載頁面,都會返回 HTML 資源以及裏面的 CSS 等靜態資源,組合成一個新的頁面。前端框架

「瞎了」的同窗,我再教一個方法,就是在瀏覽器頁面右鍵,點擊「顯示網頁源代碼」,打開後以下所示:markdown

網頁上能看到什麼圖片或文字,你能在上述圖片中找到相應的 HTML 結構,那也屬於傳統頁面,也就是 DOM 直出。

單頁面

時代在進步,科技在發展,面對日益增加的網頁需求,網頁開始走向模塊化、組件化的道路。隨之而來的是代碼的難以維護、不可控、迭代艱難等現象。面臨這種狀況,催生出很多優秀的現代前端框架,首當其衝的即是 React 、 Vue 、 Angular 等著名單頁面應用框架。而這些框架有一個共同的特色,即是「經過 JS 渲染頁面」。

舉個例子,之前咱們直出 DOM ,而如今運用這些單頁面框架以後, HTML 頁面基本上只有一個 DOM 入口,大體以下所示:

全部的頁面組件,都是經過運行上圖底部的 app.js 腳本,掛載到 <div id="root"></div> 這個節點下面。用一個極其簡單的 JS 展現掛載這一個步驟:

<body>
  <div id="root"></div>
  <script> const root = document.getElementById('root') // 獲取根節點 const divNode = document.createElement('div') // 建立 div 節點 divNode.innerText = '你媽貴姓?' // 插入內容 root.appendChild(divNode) // 插入根節點 </script>
</body>
複製代碼

image.png

脫去全部的凡塵世俗,最本真的單頁項目運行形式即是如此。 注意,我要點題了啊!!! 

image.png

既然單頁面是這樣渲染的,那若是我有十幾個頁面要互相跳轉切換,咋整!!??這時候 前端路由 應運而生,它的出現就是爲了解決單頁面網站,經過切換瀏覽器地址路徑,來匹配相對應的頁面組件。咱們經過一張醜陋的圖片來理解這個過程:

前端路由 會根據瀏覽器地址欄 pathname 的變化,去匹配相應的頁面組件。而後將其經過建立 DOM 節點的形式,塞入根節點 <div id="root"></div> 。這就達到了無刷新頁面切換的效果,從側面也能說明正由於無刷新,因此 React 、 Vue 、 Angular 等現代框架在建立頁面組件的時候,每一個組件都有本身的 生命週期 。

原理

前端路由 插件比較火的倆框架對應的就是 Vue-Router 和 React-Router ,可是它們的邏輯,歸根結底仍是同樣的,用異曲同工四個字,再合適不過。

經過分析哈希模式和歷史模式的實現原理,讓你們對前端路由的原理有一個更深入的理解。

哈希模式

a 標籤錨點你們應該不陌生,而瀏覽器地址上 # 後面的變化,是能夠被監聽的,瀏覽器爲咱們提供了原生監聽事件 hashchange ,它能夠監聽到以下的變化:

  • 點擊 a 標籤,改變了瀏覽器地址
  • 瀏覽器的前進後退行爲
  • 經過 window.location 方法,改變瀏覽器地址

接下來咱們利用這些特色,去實現一個 hash 模式的簡易路由: 在線運行

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Hash 模式</title>
</head>
  <body>
    <div>
      <ul>
        <li><a href="#/page1">page1</a></li>
        <li><a href="#/page2">page2</a></li>
      </ul>
      <!--渲染對應組件的地方-->
      <div id="route-view"></div>
    </div>
  <script type="text/javascript"> // 第一次加載的時候,不會執行 hashchange 監聽事件,默認執行一次 // DOMContentLoaded 爲瀏覽器 DOM 加載完成時觸發 window.addEventListener('DOMContentLoaded', Load) window.addEventListener('hashchange', HashChange) // 展現頁面組件的節點 var routeView = null function Load() { routeView = document.getElementById('route-view') HashChange() } function HashChange() { // 每次觸發 hashchange 事件,經過 location.hash 拿到當前瀏覽器地址的 hash 值 // 根據不一樣的路徑展現不一樣的內容 switch(location.hash) { case '#/page1': routeView.innerHTML = 'page1' return case '#/page2': routeView.innerHTML = 'page2' return default: routeView.innerHTML = 'page1' return } } </script>
  </body>
</html>
複製代碼

固然,這是很簡單的實現,真正的 hash 模式,還要考慮到不少複雜的狀況,你們有興趣就去看看源碼。

瀏覽器展現效果以下:

歷史模式

history 模式會比 hash 模式稍麻煩一些,由於 history 模式依賴的是原生事件 popstate ,下面是來自 MDN 的解釋:

image.png

小知識:pushState 和 replaceState 都是 HTML5 的新 API,他們的做用很強大,能夠作到改變瀏覽器地址卻不刷新頁面。這是實現改變地址欄卻不刷新頁面的重要方法。

包括 a 標籤的點擊事件也是不會被 popstate 監聽。咱們須要想個辦法解決這個問題,才能實現 history 模式。

image.png

**解決思路:**咱們能夠經過遍歷頁面上的全部 a 標籤,阻止 a 標籤的默認事件的同時,加上點擊事件的回調函數,在回調函數內獲取 a 標籤的 href 屬性值,再經過 pushState 去改變瀏覽器的 location.pathname 屬性值。而後手動執行 popstate 事件的回調函數,去匹配相應的路由。邏輯上可能有些饒,咱們用代碼來解釋一下: 在線地址 

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>History 模式</title>
</head>
<body>
  <div>
    <ul>
      <li><a href="/page1">page1</a></li>
      <li><a href="/page2">page2</a></li>
    </ul>
    <div id="route-view"></div>
  </div>
  <script type="text/javascript"> window.addEventListener('DOMContentLoaded', Load) window.addEventListener('popstate', PopChange) var routeView = null function Load() { routeView = document.getElementById('route-view') // 默認執行一次 popstate 的回調函數,匹配一次頁面組件 PopChange() // 獲取全部帶 href 屬性的 a 標籤節點 var aList = document.querySelectorAll('a[href]') // 遍歷 a 標籤節點數組,阻止默認事件,添加點擊事件回調函數 aList.forEach(aNode => aNode.addEventListener('click', function(e) { e.preventDefault() //阻止a標籤的默認事件 var href = aNode.getAttribute('href') // 手動修改瀏覽器的地址欄 history.pushState(null, '', href) // 經過 history.pushState 手動修改地址欄, // popstate 是監聽不到地址欄的變化,因此此處須要手動執行回調函數 PopChange PopChange() })) } function PopChange() { console.log('location', location) switch(location.pathname) { case '/page1': routeView.innerHTML = 'page1' return case '/page2': routeView.innerHTML = 'page2' return default: routeView.innerHTML = 'page1' return } } </script>
</body>
</html>
複製代碼

這裏注意,不能在瀏覽器直接打開靜態文件,須要經過 web 服務,啓動端口去瀏覽網址。

總結

這篇文章主要知識點集中在前端路由這塊,能徹底看完,而且把實現原理捋一遍,我想你應該對現代前端框架會有一個新的理解。沒有新的理解的同窗,來杭州打我,我不還手。

相關文章
相關標籤/搜索