<kbd id="5sdj3"></kbd>
<th id="5sdj3"></th>

  • <dd id="5sdj3"><form id="5sdj3"></form></dd>
    <td id="5sdj3"><form id="5sdj3"><big id="5sdj3"></big></form></td><del id="5sdj3"></del>

  • <dd id="5sdj3"></dd>
    <dfn id="5sdj3"></dfn>
  • <th id="5sdj3"></th>
    <tfoot id="5sdj3"><menuitem id="5sdj3"></menuitem></tfoot>

  • <td id="5sdj3"><form id="5sdj3"><menu id="5sdj3"></menu></form></td>
  • <kbd id="5sdj3"><form id="5sdj3"></form></kbd>

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

    共 14198字,需瀏覽 29分鐘

     ·

    2024-04-19 19:21

    哈嘍,大家好我是考拉??。

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

    當(dāng)談到前端路由時,指的是在前端應(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)系。當(dāng)你想要從服務(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標(biāo)簽,但是a標(biāo)簽有個機制,就是點擊必定會引起頁面的刷新。但瀏覽器的機制是哈希值的變更不會引起頁面刷新,所以地址放哈希值可以解決這一問題

    <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值的變更。當(dāng)我們點擊首頁的時候,下面的代碼都會執(zhí)行一次,因為hash值變了

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

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

    我們現(xiàn)在把監(jiān)聽器的回調(diào)函數(shù)寫出來,拿到當(dāng)前的哈希值,去對應(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
    }
    })
    }

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

    這樣寫會有個問題,就是頁面剛加載完畢的時候不會去加載當(dāng)前的路由,想要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標(biāo)簽
    2. 瀏覽器前進后退
    3. window.location

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

    那問題來了,history模式?jīng)]有哈希是如何實現(xiàn)的呢?沒有哈希值a標(biāo)簽一定會引起頁面的刷新,如何解決?我們繼續(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)在標(biāo)簽頁或者框架里訪問的會話歷史記錄

    ?

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


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

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

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

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

    同樣是上面的情景,兩個a標(biāo)簽,一個首頁,一個關(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標(biāo)簽有個默認(rèn)的頁面跳轉(zhuǎn)效果,既然現(xiàn)在不用哈希,我們就需要自己把a標(biāo)簽的頁面跳轉(zhuǎn)刷新效果干掉

    先拿到所有的a標(biāo)簽

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

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

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

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


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

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

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

    a.getAttribute('href')

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

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

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

    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標(biāo)簽
                // console.log(links)
                links.forEach((a) => {
                    // 禁用a標(biāo)簽的默認(rèn)跳轉(zhuǎn)行為
                    a.addEventListener('click', (e) => {
                        console.log(e)
                        e.preventDefault() // 阻止a的跳轉(zhuǎn)行為
                        history.pushState(null'', a.getAttribute('href')) // 核心方法  a.getAttribute('href')獲取a標(biāo)簽下的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)路由的兩種模式,哈希剛好就是瀏覽器承認(rè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_海豚

    ?

    最后

    最后

    Node 社群

        


    我組建了一個氛圍特別好的 Node.js 社群,里面有很多 Node.js小伙伴,如果你對Node.js學(xué)習(xí)感興趣的話(后續(xù)有計劃也可以),我們可以一起進行Node.js相關(guān)的交流、學(xué)習(xí)、共建。下方加 考拉 好友回復(fù)「Node」即可。

       “分享、點贊、在看” 支持一下

    瀏覽 152
    10點贊
    評論
    收藏
    分享

    手機掃一掃分享

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

    手機掃一掃分享

    分享
    舉報

    <kbd id="5sdj3"></kbd>
    <th id="5sdj3"></th>

  • <dd id="5sdj3"><form id="5sdj3"></form></dd>
    <td id="5sdj3"><form id="5sdj3"><big id="5sdj3"></big></form></td><del id="5sdj3"></del>

  • <dd id="5sdj3"></dd>
    <dfn id="5sdj3"></dfn>
  • <th id="5sdj3"></th>
    <tfoot id="5sdj3"><menuitem id="5sdj3"></menuitem></tfoot>

  • <td id="5sdj3"><form id="5sdj3"><menu id="5sdj3"></menu></form></td>
  • <kbd id="5sdj3"><form id="5sdj3"></form></kbd>
    在线观看免费无码 | 逼逼网址| 大香蕉超碰成人网 | 免费网站18禁 | 亚洲AV五月天草榴 |