成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

國(guó)內(nèi)存在感最低的前端API——瀏覽器路由

U2FsdGVkX1x / 3509人閱讀

摘要:最新一直在看關(guān)于和路由這塊的知識(shí),最終發(fā)現(xiàn)這些路由框架的模塊功能的實(shí)現(xiàn)都是基于瀏覽器原生路由的。在瀏覽器中實(shí)現(xiàn)前端路由主要有兩種方式一個(gè)是我們常用的,另一個(gè)是提供的。該對(duì)象的和分別表示的各個(gè)部分,它們因此被稱為分解屬性。

最新一直在看關(guān)于 Vue 和 React 路由這塊的知識(shí),最終發(fā)現(xiàn)這些路由框架的模塊功能的實(shí)現(xiàn)都是基于瀏覽器原生路由 API?的。本著追根溯源的初心,于是就想著將瀏覽器原生的路由 API 整體梳理一遍,以便更加順暢的理解 Vue-Router 和 React-Router 的相關(guān)實(shí)現(xiàn)和原理。

背景

瀏覽器的主要功能就是根據(jù)輸入的 URL 在窗口加載對(duì)應(yīng)的文檔,與此同時(shí),瀏覽器會(huì)記錄一個(gè) tab 窗口載入過(guò)的所有文檔,同時(shí)會(huì)提供 "前進(jìn)"、"后退" 和 "刷新" 的功能,以便用戶可以在這些已經(jīng)記錄的文檔之間進(jìn)行切換瀏覽和重載當(dāng)前頁(yè)面獲取最新的瀏覽信息。

這些功能的實(shí)現(xiàn)最早是在服務(wù)器端實(shí)現(xiàn)的,因?yàn)槟菚r(shí)候的引用都是前后端不分離的,頁(yè)面內(nèi)容也是動(dòng)態(tài)生成的,所以這些頁(yè)面的跳轉(zhuǎn)、切換、刷新都是在服務(wù)端實(shí)現(xiàn)的。后來(lái)出現(xiàn)了 SPA(Single Page Application 單頁(yè)應(yīng)用),頁(yè)面都是通過(guò) JavaScript 動(dòng)態(tài)生成和載入到頁(yè)面的,并且可以在無(wú)刷新的情況下加載頁(yè)面最新的狀態(tài)信息,這時(shí)候如果要提供上述的功能就需要自己進(jìn)行處理(因?yàn)榇藭r(shí)的頁(yè)面都是現(xiàn)實(shí)在同一個(gè)大的框架頁(yè)面里面的,根本不存在頁(yè)面的跳轉(zhuǎn)切換),所以催生了各種框架對(duì)應(yīng)的 Router 實(shí)現(xiàn)。

在瀏覽器中實(shí)現(xiàn)前端路由主要有兩種方式:一個(gè)是我們常用的 hash,另一個(gè)是 HTML5 提供的 history。其實(shí)還有另外一種利用 stack 實(shí)現(xiàn)的方式適用于 Node.js 服務(wù)器端,這里我們著重說(shuō)一下瀏覽器提供的 hash 和 history 吧,stack 具體怎么實(shí)現(xiàn)等我們說(shuō)到 x-Router 源碼的時(shí)候再詳細(xì)說(shuō)一下。

Hash

在瀏覽器 URL 地址欄,我們總會(huì)發(fā)現(xiàn)像這樣的地址:react.docschina.org/docs/react-… React 官網(wǎng)關(guān)于 lazy 的一個(gè)地址)。大家肯定發(fā)現(xiàn):這串 URL 的最后有以 # 號(hào)開(kāi)始的一串標(biāo)識(shí),那它到底是起著什么樣的作用呢?肯定不會(huì)平白無(wú)故的出現(xiàn)吧。

hash 特性

你可以直接在瀏覽器中打開(kāi)這個(gè)鏈接地址,你是不是發(fā)現(xiàn)頁(yè)面會(huì)自動(dòng)滾動(dòng)到(頁(yè)面頂部定位到)標(biāo)題為 React.lazy 的部分文檔。你再將頁(yè)面往上滾動(dòng),肯定會(huì)發(fā)現(xiàn)上面還有部分的文檔內(nèi)容。此時(shí),你修改地址欄的地址為?react.docschina.org/docs/react-… React.Suspense 部分。

在早些年,hash 作為 URL 的一部分主要用來(lái)定位文檔中的文檔片段。在上面的例子中,我們通過(guò)在 URL 后面添加 #reactlazy 和 #reactsuspense 定位到了文檔對(duì)應(yīng)標(biāo)題為 React.lazy 和 React.Suspense 的部分。那他們到底是怎么做到的呢?
通過(guò)審核元素我們發(fā)現(xiàn):在?React.lazy 和 React.Suspense 對(duì)應(yīng)的標(biāo)題部分分別都有一個(gè) h3 標(biāo)簽,而且標(biāo)簽的 id 屬性對(duì)應(yīng)就是我們?cè)?URL 地址欄輸入的 hash 值部分(只是少了 # 號(hào))。

hash 定位文檔片段
可能有同學(xué)會(huì)有疑惑:為什么 hash 是通過(guò)元素上面的 id 屬性來(lái)定位文檔的?前面我們提到過(guò),URL 中的 hash 部分是用來(lái)定位文檔中的文檔片段的。大家想想:所需要定位的文檔片段肯定是唯一的,不然定位肯定是不準(zhǔn)確了,那這個(gè)定位文檔就有點(diǎn)雞肋了,在文檔中標(biāo)識(shí)唯一的屬性只有是 id 了,如果是我,我也會(huì)通過(guò) hash 匹配元素的 id 來(lái)定位文檔?,F(xiàn)在來(lái)驗(yàn)證一下我們的猜想:

1、首先在新的 tab 窗口打開(kāi)?react.docschina.org/docs/react-… 頁(yè)面,然后在審核元素下找到上圖所展示的 DOM 元素,修改其中的 h3 標(biāo)簽的 id 屬性值為 reactlazy1,接著在 URL 地址欄追加 #reactlazy hash 值并按下回車鍵,此時(shí)頁(yè)面并沒(méi)有定位到標(biāo)題為 React.lazy 的文檔片段,最后將 URL 地址欄的 #reactlazy hash 值改成?#reactlazy1 hash 值并按下回車鍵,此時(shí)頁(yè)面并沒(méi)有定位到標(biāo)題為 React.lazy 的文檔片段,這一系列的表現(xiàn)說(shuō)明 hash 定位還是和元素的 id 屬性值還是有關(guān)聯(lián)的;

2、依然是在新的 tab 窗口打開(kāi)?react.docschina.org/docs/react-… 頁(yè)面,然后將頁(yè)面手動(dòng)滾動(dòng)到標(biāo)題為 React.lazy 的文檔片段,將鼠標(biāo)放在標(biāo)題上會(huì)出現(xiàn)一個(gè)錨點(diǎn)的圖標(biāo),點(diǎn)擊圖標(biāo)發(fā)現(xiàn)頁(yè)面定位到了標(biāo)題為 React.lazy 的文檔片段并且 URL 地址欄變成了?react.docschina.org/docs/react-…?#reactlazy hash 值。此時(shí)再回頭看看我們前面給出的截圖發(fā)現(xiàn) id 屬性值為?reactlazy 的 h3 標(biāo)簽中有一個(gè) href 屬性值為 #reactlazy 的 a 標(biāo)簽,其實(shí)我們?cè)陧?yè)面上看到的錨點(diǎn)圖標(biāo)就是這個(gè) a 標(biāo)簽的展示。當(dāng)我們點(diǎn)擊錨點(diǎn)圖標(biāo)就是點(diǎn)擊了 a 鏈接,然后將 url 定位到了?id 屬性值為?reactlazy 的 h3 標(biāo)簽,還是很好的說(shuō)明了 hash 定位還是和元素的 id 屬性值還是有關(guān)聯(lián)的;

3、MDN 官方定義如下:

MDN 官方文檔上有明確的定義,但是我們還是通過(guò)兩個(gè)方面來(lái)證明了我們的推論,乍一看好像說(shuō)了很多沒(méi)有用的東西,其實(shí)這樣反復(fù)的推敲更有利于我們深刻的理解相關(guān)的知識(shí)點(diǎn)以及為什么是這樣,而不是那樣!

hash 路由

hash 的存在除了可以通過(guò)設(shè)置文檔中元素的 ID 來(lái)定位文檔片段之外,還可以設(shè)置為任意的字符串來(lái)表示路由。在 Vue、React 等現(xiàn)代前端框架中,為了實(shí)現(xiàn)功能完備的 SPA 應(yīng)用都配備了對(duì)應(yīng)的路由系統(tǒng)。在這些路由系統(tǒng)都會(huì)提供 hash 路由模式。

在 hash 模式下,hash 會(huì)支持任意的字符串來(lái)表示對(duì)應(yīng)的 URL。這些路由系統(tǒng)針對(duì) hash 模式的實(shí)現(xiàn)基本都是大同小異:在設(shè)置 location.hash 屬性值后,應(yīng)用就會(huì)想盡一切辦法檢測(cè)狀態(tài)值變化,以便能夠讀取出存儲(chǔ)在片段標(biāo)識(shí)符中的狀態(tài)并相應(yīng)地更新自己的狀態(tài)。支持 HTML5 的瀏覽器一旦發(fā)現(xiàn)片段標(biāo)識(shí)符發(fā)生了變化,就會(huì)在 Window 對(duì)象上觸發(fā) hashchange 事件,這時(shí)就會(huì)觸發(fā)對(duì)象的函數(shù)處理邏輯 —— 對(duì)?location.hash 的值進(jìn)行解析,然后使用該值包含的狀態(tài)信息重新渲染應(yīng)用。

這里只是提到了一個(gè)基礎(chǔ)的思路,路由系統(tǒng)的具體實(shí)現(xiàn),后續(xù)會(huì)娓娓道來(lái)!

hash 事件

// 在 window 下監(jiān)聽(tīng) hashchange 事件
window.onhashchange = function() {
  // 當(dāng)事件觸發(fā)時(shí)輸出當(dāng)前的 hash 值
  console.log(window.location.hash)
}

在不支持 HTML5 的瀏覽器中,我們可以通過(guò) 100ms 輪詢監(jiān)聽(tīng) url 變化來(lái)模擬:

(function(window){
  // 如果瀏覽器不支持原生實(shí)現(xiàn)的事件,則開(kāi)始模擬,否則退出。
  if ( "onhashchange" in window.document.body ) return;
  
  var location = window.location,
      oldUrl = location.href,
      oldHash = location.hash;

  // 每隔 100ms 檢查 hash 是否發(fā)生變化
  setInterval(function() {
    var newUrl = location.href,
        newHash = location.hash;

    // hash 發(fā)生變化且全局注冊(cè)有 onhashchange 方法(這個(gè)名字是為了和模擬的事件名保持統(tǒng)一);
    if (newHash !== oldHash && typeof window.onhashchange === "function"  ) {
      // 執(zhí)行方法
      window.onhashchange({
        type: "hashchange",
        oldURL: oldUrl,
        newURL: newUrl
      });

      oldUrl = newUrl;
      oldHash = newHash;
    }
  }, 100);
})(window)

??注意:設(shè)置 location.hash 屬性會(huì)更新顯示在地址欄中的 URL,同時(shí)會(huì)在瀏覽器的歷史記錄中添加一條記錄。

History

為了標(biāo)準(zhǔn)化管理瀏覽器歷史管理,HTML5 定義了相對(duì)復(fù)雜的 API —— history。

history api

1、history 里面新增了兩個(gè) API,history.pushState() 和 history.replaceState()。這兩個(gè) API 都接受同樣的參數(shù):

它們之間的不同之處是:history.pushState() 方法是將新?tīng)顟B(tài)添加到瀏覽器的歷史記錄中,也就是說(shuō)還可以通過(guò)點(diǎn)擊?"后退"?按鈕,退到前一個(gè)頁(yè)面;history.replaceState() 是用新的狀態(tài)代替當(dāng)前的歷史狀態(tài),也就是說(shuō)沒(méi)有更多的歷史記錄,"后退"?按鈕不能操作了,頁(yè)面不能?"后退"?了。

??注意:當(dāng)執(zhí)行這兩個(gè) API 時(shí),瀏覽器的 URL 地址欄會(huì)變化,但是頁(yè)面內(nèi)容不會(huì)刷新!

狀態(tài)對(duì)象(state):**?一個(gè) JavaScript 對(duì)象,該對(duì)象包含用于恢復(fù)當(dāng)前文檔所需的所有信息。可以是任何能夠通過(guò) JSON.stringify() 方法轉(zhuǎn)換成相應(yīng)字符串形式的對(duì)象,也可以是其他類似 Date、RegExp 這樣特定的本地類型。

標(biāo)題(title):**瀏覽器可以使用它標(biāo)識(shí)瀏覽歷史記錄中保存的狀態(tài),可以傳一個(gè)空字符串,也可以傳入一個(gè)簡(jiǎn)短的標(biāo)題,標(biāo)明將要進(jìn)入的狀態(tài)。

地址(URL):**用來(lái)表示當(dāng)前狀態(tài)的位置。新的 URL 不一定是絕對(duì)路徑;如果是相對(duì)路徑,它將以當(dāng)前 URL 為基準(zhǔn)(類似 #reactlazy 這樣的 hash);傳入的 URL 與當(dāng)前 URL 應(yīng)該是同源的,否則 pushState() 會(huì)拋出異常。該參數(shù)是可選的;不指定的話則為文檔當(dāng)前 URL。

為此,我們可以利用語(yǔ)雀網(wǎng)站做一系列的實(shí)驗(yàn):

window.history.pushState(null, null, "https://www.yuque.com/dashboard/?name=littleLane");
// result: https://www.yuque.com/dashboard/?name=littleLane

window.history.pushState(null, null, "https://www.yuque.com/dashboard/name/littleLane");
//result: https://www.yuque.com/dashboard/name/littleLane

window.history.pushState(null, null, "?name=littleLane");
//result: https://www.yuque.com/dashboard?name=littleLane

window.history.pushState(null, null, "name=littleLane");
//result: https://www.yuque.com/dashboard/name=littleLane

window.history.pushState(null, null, "/name/littleLane");
//result: https://www.yuque.com/dashboard/name/littleLane

在控制臺(tái)中執(zhí)行上面一系列語(yǔ)句時(shí),瀏覽器的 URL 變化成了我們備注的 result 的結(jié)果,但是頁(yè)面并沒(méi)有發(fā)生重渲染,還有當(dāng)我們每次執(zhí)行 pushState 時(shí),瀏覽器歷史都會(huì)添加一條記錄,大家可以通過(guò)?"后退"?按鈕進(jìn)行查看。大家執(zhí)行完上面的測(cè)試語(yǔ)句后,還可以將 pushState 替換成 replaceState 再次進(jìn)行一輪測(cè)試,此時(shí)新的瀏覽記錄都會(huì)代替當(dāng)前的歷史記錄,還是可以通過(guò)?"后退"?按鈕進(jìn)行查看。

??注意:這里的 url 不支持跨域,當(dāng)我們把?www.yuque.com?換成?www.baidu.com?時(shí)就會(huì)報(bào)錯(cuò)。

2、除了上面新增的 API,history 對(duì)象上還有表示瀏覽歷史列表數(shù)量的 length 屬性,還定義了 back()、forward() 和 go() 進(jìn)行瀏覽記錄切換的方法。
History 對(duì)象的 back() 和 forward() 方法與瀏覽器的 "后退" 和 "前進(jìn)" 按鈕功能一樣:它們可以使瀏覽器在瀏覽歷史中后退或前進(jìn)跳轉(zhuǎn)一格。而 go() 方法會(huì)接受一個(gè)整數(shù),可以在瀏覽歷史列表中向前(接受正整數(shù)參數(shù))或向后(接受負(fù)整數(shù)參數(shù))跳過(guò)任意多個(gè)頁(yè)。比如 history.go(-1) 就會(huì)向后跳轉(zhuǎn)一頁(yè),history.go(0) 就是刷新當(dāng)前頁(yè),history.go(1) 就會(huì)向前跳轉(zhuǎn)一頁(yè)。

history 事件 - popstate

當(dāng)用戶通過(guò)?"前進(jìn)" 和 "后退" 按鈕瀏覽保存的歷史狀態(tài)時(shí),瀏覽器會(huì)在 Window 對(duì)象上觸發(fā)一個(gè) popstate 事件。與該事件相關(guān)的事件對(duì)象有一個(gè) state 屬性,該屬性包含傳遞給 pushState() 方法的狀態(tài)對(duì)象的副本。

// 在 window 下監(jiān)聽(tīng) onpopstate 事件
window.onpopstate = function(state) {
  // 當(dāng) onpopstate 事件 (用戶通過(guò) "前進(jìn)" 和 "后退" 按鈕切換瀏覽記錄) 觸發(fā)時(shí)輸出當(dāng)前狀態(tài)
  console.log(state)
}
Location

Window 對(duì)象的 location 屬性和 Document 對(duì)象的 location 屬性引用的都是 Location 對(duì)象,它用來(lái)表示該窗口中當(dāng)前顯示的文檔的 URL,并定義了方法來(lái)使窗口載入新的文檔。

window.location === document.location  // 總是返回 true

解析 URL

Location 對(duì)象的 href 屬性是一個(gè)字符串,表示當(dāng)前 URL 的完整文本。Location 對(duì)象的 toString() 方法返回 href 屬性的值,因此在會(huì)隱式調(diào)用 toString() 的情況下,可以使用 location 代替 location.href。

該對(duì)象的 protocol、host、hostname、port、pathname 和 search 分別表示 URL 的各個(gè)部分,它們因此被稱為 URL 分解屬性。一般我們用的比較多的就是提取 URL 里面的參數(shù)了:

// 獲取地址欄參數(shù)
const getUrlParame = (paramName) => {
    const urlParams = {};
    let params = window.location.search.substring(1);
    if (!params) {
        return;
    }
    params = params.split("&");
    for (let i = 0; i < params.length; i += 1) {
        let item = params[i];
        item = item.split("=");
        urlParams[item[0]] = decodeURIComponent(item[1]);
    }
    if (paramName) {
        return urlParams[paramName];
    }
    return urlParams;
};

載入新文檔

Location 對(duì)象的 assign() 方法可以使窗口載入并顯示指定的 url 中的文檔。replace() 方法也有類似的功能,但是它會(huì)在新文檔載入之前將當(dāng)前文檔從瀏覽歷史中刪除,就是說(shuō) "后退" 按鈕并不會(huì)將瀏覽器帶到原始的文檔。
Location 對(duì)象還定義可 reload() 方法用來(lái)重新載入當(dāng)前文檔。

總結(jié)

上述的內(nèi)容我們主要了解了在瀏覽器中支持的兩種路由模式 —— hash 和 history,然后對(duì)它們各自的特性、api 和對(duì)應(yīng)的事件做了詳細(xì)的講解,后面又說(shuō)到了瀏覽器路由中至關(guān)重要的對(duì)象 —— Location,這一系列的內(nèi)容為我們后續(xù)理解 Vue-Router、React-Router 等路由系統(tǒng)的實(shí)現(xiàn)和閱讀源碼打下了堅(jiān)實(shí)的基礎(chǔ)。

作者:littleLane

本文首發(fā)微信公眾號(hào):qianduanshenru

歡迎掃描二維碼關(guān)注公眾號(hào),每天都給你推送新鮮的前端技術(shù)文章

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/103926.html

相關(guān)文章

  • 國(guó)內(nèi)存在最低前端API——覽器路由

    摘要:最新一直在看關(guān)于和路由這塊的知識(shí),最終發(fā)現(xiàn)這些路由框架的模塊功能的實(shí)現(xiàn)都是基于瀏覽器原生路由的。在瀏覽器中實(shí)現(xiàn)前端路由主要有兩種方式一個(gè)是我們常用的,另一個(gè)是提供的。該對(duì)象的和分別表示的各個(gè)部分,它們因此被稱為分解屬性。 最新一直在看關(guān)于 Vue 和 React 路由這塊的知識(shí),最終發(fā)現(xiàn)這些路由框架的模塊功能的實(shí)現(xiàn)都是基于瀏覽器原生路由 API?的。本著追根溯源的初心,于是就想著將瀏覽...

    tolerious 評(píng)論0 收藏0
  • 2019年前端3個(gè)趨勢(shì)

    摘要:組件成為前端最基本的物料,融合在組件中的方案日趨成熟。組件成為最基本的前端物料,讓組件化更徹底在的調(diào)研報(bào)告中,開(kāi)發(fā)者有愿意繼續(xù),有愿意繼續(xù)。需要留意的是,有表示對(duì)感興趣,因此獲得的最感興趣獎(jiǎng)。 簡(jiǎn)介: JavaScript 應(yīng)用范圍廣泛,靜態(tài)類型語(yǔ)言 TypeScript 會(huì)繼續(xù)得到更多開(kāi)發(fā)者的青睞。 組件成為前端最基本的物料,CSS 融合在組件中(CSS in JS)的方案日趨成熟...

    yanwei 評(píng)論0 收藏0
  • 如何打造一個(gè)令人愉悅前端開(kāi)發(fā)環(huán)境(三)

    摘要:的最后一個(gè)大招就是替換一些傳統(tǒng)的服務(wù)端語(yǔ)言,例如,,等,在業(yè)務(wù)層上面使用來(lái)開(kāi)發(fā)服務(wù)端完全不成問(wèn)題。更多的的使用細(xì)節(jié)和技巧建議關(guān)注美團(tuán)博客大搜車論壇下一篇我們開(kāi)啟如何結(jié)合和搭建一個(gè)開(kāi)發(fā)環(huán)境和項(xiàng)目目錄 往期回顧 前面2期都講得是瀏覽器端的東西比較多,包括Webpack,雖然是Node處理的,但是還是瀏覽器端用的多,對(duì)于現(xiàn)在的前端開(kāi)發(fā)來(lái)說(shuō),不懂一點(diǎn)服務(wù)端的東西,簡(jiǎn)直沒(méi)辦法活,一般的招聘要求都...

    cgh1999520 評(píng)論0 收藏0
  • 前端監(jiān)控?cái)?shù)據(jù)采集更高效

    摘要:如何在新的技術(shù)背景下讓前端數(shù)據(jù)采集工作更加完善高效,是本文討論的重點(diǎn)。具體來(lái)說(shuō),我們對(duì)前端的數(shù)據(jù)采集具體主要分為路由切換性能資源錯(cuò)誤日志上報(bào)路由切換等前端技術(shù)的快速發(fā)展使單頁(yè)面應(yīng)用盛行。 隨著業(yè)務(wù)的快速發(fā)展,我們對(duì)生產(chǎn)環(huán)境下的問(wèn)題感知能力越來(lái)越關(guān)注。作為距離用戶最近的一層,前端的表現(xiàn)是否可靠、穩(wěn)定、好用,很大程度上決定著用戶對(duì)整個(gè)產(chǎn)品的體驗(yàn)和感受。因此,對(duì)于前端的監(jiān)控不容忽視。 搭建一...

    Half 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<