<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          【面試題】2055- 談?wù)勄岸寺酚傻膶崿F(xiàn)原理【hash&history】

          共 13946字,需瀏覽 28分鐘

           ·

          2024-05-23 09:35

          今天我們來聊一聊前端路由。

          當談到前端路由時,指的是在前端應(yīng)用中管理頁面導(dǎo)航和URL的機制。前端路由使得單頁應(yīng)用(Single-Page Application,SPA)能夠在用戶與應(yīng)用交互時動態(tài)地加載不同的視圖,而無需每次都重新加載整個頁面。

          在前端開發(fā)中,常用的前端路由庫有很多,比如React Router、Vue Router和Angular Router等。這些庫提供了一組API和組件,用于定義路由規(guī)則、處理導(dǎo)航事件和渲染相應(yīng)的視圖。

          簡單了解前端路由后,那么前端路由實現(xiàn)的原理是什么呢?

          請看今天的分享:


          ?

          vue-router是前端路由,但是前端路由不是vue-router,這是個包含關(guān)系

          ?

          路由

          路由一詞最早來自服務(wù)器,和前端沒有關(guān)系。當你想要從服務(wù)器中讀取某個盤的文件,這個文件的路徑就是路由。也就是說「路由是服務(wù)器端用來描述路徑的,或者是說url和文件的映射關(guān)系」

          后來因為前端的SPA單頁應(yīng)用,前端也借鑒了路由這個概念。瀏覽器的url變了需要映射到頁面的某個組件,url變了需要展示某個組件。/home和Home.vue,/about和About.vue就是一一映射的關(guān)系。「前端借鑒路由的稱呼來描述url和組件的映射關(guān)系」。這個時候你就想起來router中index.js文件中,一個path對應(yīng)一個component,也就是一個路徑對應(yīng)一個組件

          實現(xiàn)路由需要解決的問題

          1. 如何修改url還不引起頁面的刷新
          2. 如何知道url變化了

          若是能解決這兩個問題就可以實現(xiàn)前端路由了。

          哈希Hash

          哈希是一種值,按照某種規(guī)則生成的一串值,用來代表一個唯一的文件,文件名后加一個哈希值,可以看到文件是否被修改過。

          在瀏覽器中也有hash這個概念,url中接一個##后的值就是哈希值,按道理url變了,頁面一定會刷新,但是哈希是個特例,放個哈希值就是不會刷新頁面,這樣,我們就解決了第一個問題,修改url不引起頁面的刷新

          「在瀏覽器url后加個哈希值,哈希值的變更不會引起瀏覽器頁面的刷新」

          下面利用哈希模式實現(xiàn)路由

          哈希手搓一個路由

          我們新建一個hash.html文件,放兩個a標簽,但是a標簽有個機制,就是點擊必定會引起頁面的刷新。但瀏覽器的機制是哈希值的變更不會引起頁面刷新,所以地址放哈希值可以解決這一問題

          <ul>
              <li><a href="#/home">首頁</a></li> 
              <li><a href="#/about">關(guān)于</a></li>
          </ul>

          <div id="routeView">
                  <!-- 放一個代碼片段 點擊首頁首頁代碼片段生效,反之關(guān)于生效-->

          </div>

          現(xiàn)在模擬一個場景,如果點擊首頁,routeView容器展示首頁的內(nèi)容,點擊關(guān)于routeView容器展示關(guān)于頁面的內(nèi)容,如果能夠?qū)崿F(xiàn),路由就可以實現(xiàn)了

          自行封裝一個路由,先寫一個路由的映射關(guān)系

          <script>
              const routes = [
                  {
                      path'#/home',
                      component'首頁內(nèi)容'
                  },
                  {
                      path'#/about',
                      component'關(guān)于頁面內(nèi)容'
                  }
              ]
          </script>

          點擊首頁,展示首頁內(nèi)容,點擊關(guān)于,展示關(guān)于首頁內(nèi)容

          接下來的事情就是點擊url,我們需要知道url的變化。我們不可能給按鈕添加一個點擊事件,如果項目大起來,按鈕很多,每次點擊一個按鈕都判斷一次url的變化,會非常的不優(yōu)雅。

          js自帶一個hashchange事件,它可以自動監(jiān)聽hash值的變更。當我們點擊首頁的時候,下面的代碼都會執(zhí)行一次,因為hash值變了

          window.addEventListener('hashchange', () => {
           console.log('changed')
          })

          這樣,第二個問題我們已經(jīng)解決了。非常之簡單!

          我們現(xiàn)在把監(jiān)聽器的回調(diào)函數(shù)寫出來,拿到當前的哈希值,去對應(yīng)component。

          window.addEventListener('hashchange', onHashChange)

          function onHashChange({
           console.log(location)
          }

          location代表window窗口的url,我們運行打印這個location看看

          1.png

          看到?jīng)]有,里面剛好有個hash值,我們可以把這個拿出來去對應(yīng)component!

          這個時候直接去數(shù)組中匹配就可以,forEach遍歷

          function onHashChange() {
          console.log(location)
          routes.forEach((item, index) => {
          if(item.path === location.hash) {
          routeView.innerHTML = item.component
          }
          })
          }

          當然,記得拿到routeView的dom結(jié)構(gòu)

          這樣寫會有個問題,就是頁面剛加載完畢的時候不會去加載當前的路由,想要hashchange在頁面初次加載的時候觸發(fā)一次,那就給一個監(jiān)聽dom結(jié)構(gòu)的事件,dom一出來就會執(zhí)行,也就是說頁面加載完畢就調(diào)用一次hashchange

          window.addEventListener('DOMContentLoaded', onHashChange)

          好了,最終的hash.html如下

          <body>
              <!-- 模擬單頁頁面應(yīng)用 -->
              <ul>
                  <li><a href="#/home">首頁</a></li> 
                  <li><a href="#/about">關(guān)于</a></li>
                  <!-- 判斷url的變化,綁定點擊事件不好,頁面過多就很累贅,有個hashchange的官方方法 -->
              </ul>

              <div id="routeView">
                  <!-- 放一個代碼片段 點擊首頁首頁代碼片段生效,反之關(guān)于生效-->

              </div>
              <script>
                  const routes = [
                      {
                          path'#/home',
                          component'首  容'
                      },
                      {
                          path'#/about',
                          component'關(guān)于頁面內(nèi)容'
                      }
                  ]
                  
                  const routeView = document.getElementById('routeView')
                  window.addEventListener('DOMContentLoaded', onHashChange) // 與vue的聲明周期一個道理,dom一加載完畢就觸發(fā)
                  window.addEventListener('hashchange', onHashChange)
                  
                  function onHashChange({
                      console.log(location) // url詳情,里面就有個hash值  liveserver可以幫你把html跑成服務(wù)器
                      routes.forEach((item, index) => {
                          if(item.path === location.hash) {
                              routeView.innerHTML = item.component
                          }
                      })
                  }
              
          </script>
          </body>

          其實這就是vue-router中兩種模式之一哈希模式,哈希模式就是這樣是實現(xiàn)的。

          修改地址欄

          1. a標簽
          2. 瀏覽器前進后退
          3. window.location

          以上方式導(dǎo)致url變更都會觸發(fā)hashchange事件。

          那問題來了,history模式?jīng)]有哈希是如何實現(xiàn)的呢?沒有哈希值a標簽一定會引起頁面的刷新,如何解決?我們繼續(xù)看下去

          ?

          history用得更多,二者沒有本質(zhì)區(qū)別,僅僅是因為哈希模式的url多了個#很丑,所以用的少

          ?

          history手搓一個路由

          我們先看下history在mdn中的介紹

          History - Web API 接口參考 | MDN (mozilla.org)(https://developer.mozilla.org/zh-CN/docs/Web/API/History)

          ?

          文檔中介紹:history接口允許操作瀏覽器的曾經(jīng)在標簽頁或者框架里訪問的會話歷史記錄

          ?

          我們重點看一個history自帶的方法pushState


          它可以修改url且不引起頁面的刷新

          瀏覽器中有個會話歷史棧,它可以維護你的訪問路徑,有了這個你返回就可以按照棧的順序進行前進回退。

          pushState提到了popState,他是靠popState監(jiān)聽url的改變的,并且僅當瀏覽器前進后退時生效

          既然如此,我們現(xiàn)在開始手搓

          同樣是上面的情景,兩個a標簽,一個首頁,一個關(guān)于頁面。

          <ul>
              <li><a href="/home">首頁</a></li> 
              <li><a href="/about">關(guān)于</a></li>
          </ul>

          <div id="routeView">

          給個url和組件的對應(yīng)關(guān)系數(shù)組,已經(jīng)不用哈希了

          <script>
              const routes = [
                  {
                      path'/home',
                      component'首頁內(nèi)容'
                  },
                  {
                      path'/about',
                      component'<h1>關(guān)于頁面內(nèi)容</h1>'
                  }
           ]
          </script>

          a標簽有個默認的頁面跳轉(zhuǎn)效果,既然現(xiàn)在不用哈希,我們就需要自己把a標簽的頁面跳轉(zhuǎn)刷新效果干掉

          先拿到所有的a標簽

          const links = document.querySelectorAll('li a')

          再去禁用掉默認的跳轉(zhuǎn)行為,它跳轉(zhuǎn)一定會帶來刷新,要干掉它

          links.forEach(a => {
           a.addEventListener('click', (e) => {
            console.log(e)
            e.preventDefault() // 阻止a的跳轉(zhuǎn)行為
           })
          })

          我們可以打印看看這個事件參數(shù),順著原型鏈找到event對象,里面有個preventDefault,這個就是禁用a標簽?zāi)J的跳轉(zhuǎn)行為


          接下來添加一個可以修改url又不引起頁面刷新的方法,就是pushState,具體用法查看mdn

          他有三個參數(shù),第一個參數(shù)是JavaScript對象,一般不需要,給個null就好,第二個參數(shù)由于歷史原因,寫個空字符,不寫會有問題,第三個參數(shù)是新的url

          新的url肯定是點了什么放什么url,所以我需要讀取到a標簽的href值

          a.getAttribute('href')

          以上方法是核心,這里已經(jīng)實現(xiàn)了哈希一樣的效果,并且沒有難看的#pushState的核心原理就是它會往瀏覽器的歷史棧中塞一個值進去,讓瀏覽器顯示新的值,并且不引起頁面的刷新

          接下來就是要去感知到url的變化,去一一對應(yīng)組件的展示

          我們寫一個函數(shù),來實現(xiàn)這個功能。還是一樣的,先拿到當前的瀏覽器地址

          location.pathname

          然后再進行遍歷,去添加組件

          routes.forEach((item) => {
          if(item.path === location.pathname) {
          routeView.innerHTML = item.component
          }
          })

          同樣的,我們需要在頁面初次加載的時候調(diào)用函數(shù)

          但是瀏覽器的前進后退沒有觸發(fā)上面的遍歷函數(shù),popState剛好填補這個空缺

          window.addEventListener('popState', onPopState)

          好了,最終的history.html如下

          <body>
              <ul>
                  <li><a href="/home">首頁</a></li> 
                  <li><a href="/about">關(guān)于</a></li>
              </ul>

              <div id="routeView">

              </div>

              <script>
                  const routes = [
                      {
                          path'/home',
                          component'首頁內(nèi)容'
                      },
                      {
                          path'/about',
                          component'<h1>關(guān)于頁面內(nèi)容</h1>'
                      }
                  ]
                  
                  const routeView = document.getElementById('routeView')

                  window.addEventListener('DOMContentLoaded', onLoad)
                  window.addEventListener('popstate', onPopState)

                  function onLoad({
                      const links = document.querySelectorAll('li a'// 獲取所有的li下的a標簽
                      // console.log(links)
                      links.forEach((a) => {
                          // 禁用a標簽的默認跳轉(zhuǎn)行為
                          a.addEventListener('click', (e) => {
                              console.log(e)
                              e.preventDefault() // 阻止a的跳轉(zhuǎn)行為
                              history.pushState(null'', a.getAttribute('href')) // 核心方法  a.getAttribute('href')獲取a標簽下的href屬性
                              // 映射對應(yīng)的dom
                              onPopState()
                          })
                      })
                  }

                  function onPopState({
                      console.log(location.pathname)
                      routes.forEach((item) => {
                          if(item.path === location.pathname) {
                              routeView.innerHTML = item.component
                          }
                      })
                  }
              
          </script>
          </body>

          效果如下


          最后

          本期文章主要介紹了路由這個概念,以及重點講前端實現(xiàn)路由的兩種模式,哈希剛好就是瀏覽器承認他,接了哈希值改變url不會引起頁面的刷新,然后通過location.hash得知哈希值;history就是有個方法,可以改變url不引起頁面刷新的pushState,通過location.pathname得知url,這個模式下前進回退需要通過popState事件來觸發(fā)。

          ?

          本次學(xué)習(xí)代碼已上傳至GitHub學(xué)習(xí)倉庫:(https://github.com/DolphinFeng/CodeSpace)

          ?
          ?

          原文地址:https://juejin.cn/post/7321049431489544229

          作者:Dolphin_海豚

          ?

          瀏覽 45
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  精品内射一区 | 青青草成人在线免费观看视频 | 蜜桃传媒AV | 午夜一级视频 | 中文字幕成人视频 |