衆所周知,vue和react是你們廣泛在使用的前端框架,而框架在構建單頁面應用的時候都缺乏不了路由, vue對應的有vue-router,react對應的有react-router-dom,而在react-router-dom以前有一個叫作react-router的依賴,那麼它們兩個有什麼區別呢html
react-router-dom: 基於react-router,加入了在瀏覽器運行環境下的一些功能,例如:Link組件前端
也就是說react-router有的組件或者子項,react-router-dom必定有vue
BrowserRouter和HashRouter 這兩個組件,前者使用pushState和popState事件構建路由,基於history模式,後者使用window.location.hash和hashchange事件構建路由,基於hash模式,那麼什麼是history,什麼是hash呢?html5
先說說hashreact
一、#的涵義ajax
#表明網頁中的一個位置。右面的字符就是表明的位置信息:如vue-router
http://localhost:8081/cbuild/index.html#first就表明網頁index.html的first位置。瀏覽器讀取這個URL後,會自動將first位置滾動至可視區域。瀏覽器
爲網頁制定標識符: 一是使用錨點,好比。 二是使用id屬性,好比<divid="print" >。安全
二、HTTP請求不包括#bash
好比:http://localhost:8081/cbuild/index.html#first
瀏覽器實際發出的請求是這樣的: GET /index.html HTTP/1.1 不包含#first
三、#後的字符
在第一個#後面出現的任何字符,都會被瀏覽器解讀爲位置標識符。這意味着,這些字符都不會被髮送到服務器端。 好比,下面URL的原意是指定一個顏色值: www.example.com/?color=#fff 可是,瀏覽器實際發出的請求是: GET /?color= HTTP/1.1 Host: www.example.com 能夠看到,"#fff"被省略了。只有將#轉碼爲%23,瀏覽器纔會將其做爲實義字符處理。也就是說,上面的網址應該被寫成: example.com/?color=%23f…
四、改變#不觸發網頁重載
單單改變#後的部分,瀏覽器只會滾動到相應位置,不會從新加載網頁。 好比,從 www.example.com/index.html#… 改爲 www.example.com/index.html#… 瀏覽器不會從新向服務器請求index.html。
五、改變#會改變瀏覽器的訪問歷史
每一次改變#後的部分,都會在瀏覽器的訪問歷史中增長一個記錄,使用"後退"按鈕,就能夠回到上一個位置。 這對於ajax應用程序特別有用,能夠用不一樣的#值,表示不一樣的訪問狀態,而後向用戶給出能夠訪問某個狀態的連接。 值得注意的是,上述規則對IE6和IE7不成立,它們不會由於#的改變而增長曆史記錄。
六、window.location.hash讀取#值
window.location.hash這個屬性可讀可寫。讀取時,能夠用來判斷網頁狀態是否改變;寫入時,則會在不重載網頁的前提下,創造一條訪問歷史記錄。
七、onhashchange事件
這是一個HTML 5新增的事件,當#值發生變化時,就會觸發這個事件。IE8+、Firefox 3.6+、Chrome 5+、Safari 4.0+支持該事件。 它的使用方法有三種: window.onhashchange = func;
window.addEventListener("hashchange",func, false); 對於不支持onhashchange的瀏覽器,能夠用setInterval監控location.hash的變化。八、Google抓取#的機制
默認狀況下,Google的網絡蜘蛛忽視URL的#部分。 可是,Google還規定,若是你但願Ajax生成的內容被瀏覽引擎讀取,那麼URL中可使用"#!",Google會自動將其後面的內容轉成查詢字符串_escaped_fragment_的值。 好比,Google發現新版twitter的URL以下: twitter.com/#!/username 就會自動抓取另外一個URL: twitter.com/?escaped_fragment=/username 經過這種機制,Google就能夠索引動態的Ajax內容。
當URL的片斷標識符更改時,將觸發hashchange事件 (跟在#符號後面的URL部分,包括#符號),而後根據hash值作些路由跳轉處理的操做.具體參數能夠訪問location查看
最基本的路由實現方法監聽事件根據location.hash判斷界面
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<meta http-equiv="X-UA-Compatible" content="ie=edge"/>
<title>Document</title>
</head>
<body>
<ul>
<li>
<a href="#/a">a</a>
</li>
<li>
<a href="#/b">b</a>
</li>
<li>
<a href="#/c">c</a>
</li>
</ul>
<div id="view"></div>
<script>
var view = null;
// 頁面加載完不會觸發 hashchange,這裏主動觸發一次 hashchange 事件,該事件快於onLoad,因此須要在這裏操做
window.addEventListener('DOMContentLoaded', function () {
view = document.querySelector('#view');
viewChange();
});
// 監聽路由變化
window.addEventListener('hashchange', viewChange);
// 渲染視圖
function viewChange() {
switch (location.hash) {
case '#/b':
view.innerHTML = 'b';
break;
case '#/c':
view.innerHTML = 'c';
break;
default:
view.innerHTML = 'a';
break;
}
}
</script>
</body>
</html>
複製代碼
首先咱們在瀏覽器裏來看一下history
History.length (只讀)
返回一個整數,該整數表示會話歷史中元素的數目,包括當前加載的頁。例如,在一個新的選項卡加載的一個頁面中,這個屬性返回1。
History.state (只讀)
返回一個表示歷史堆棧頂部的狀態的值。這是一種能夠沒必要等待popstate 事件而查看狀態而的方式。
History.scrollRestoration
容許Web應用程序在歷史導航上顯式地設置默認滾動恢復行爲。此屬性能夠是自動的(auto)或者手動的(manual)。
History.back()
前往上一頁, 用戶可點擊瀏覽器左上角的返回按鈕模擬此方法. 等價於 history.go(-1).
注意:當瀏覽器會話歷史記錄處於第一頁時調用此方法沒有效果,並且也不會報錯。
History.forward()
在瀏覽器歷史記錄裏前往下一頁,用戶可點擊瀏覽器左上角的前進按鈕模擬此方法. 等價於 history.go(1).
注意:當瀏覽器歷史棧處於最頂端時( 當前頁面處於最後一頁時 )調用此方法沒有效果也不報錯。。
History.go()
經過當前頁面的相對位置從瀏覽器歷史記錄( 會話記錄 )加載頁面。好比:參數爲-1的時候爲上一頁,參數爲1的時候爲下一頁. 當整數參數超出界限時,例如: 若是當前頁爲第一頁,前面已經沒有頁面了,我傳參的值爲-1,那麼這個方法沒有任何效果也不會報錯。調用沒有參數的 go() 方法或者不是整數的參數時也沒有效果。( 這點與支持字符串做爲url參數的IE有點不一樣)。傳0會刷新當前頁面。
不會當即加載頁面的狀況下改變了當前URL地址,往歷史記錄添加一條條目,除非刷新頁面等操做
history.pushState(state, title , URL);
複製代碼
狀態對象
state是一個JavaScript對象,popstate事件的state屬性包含該歷史記錄條目狀態對象的副本。
狀態對象能夠是能被序列化的任何東西。緣由在於Firefox將狀態對象保存在用戶的磁盤上,以便在用戶重啓瀏覽器時使用,咱們規定了狀態對象在序列化表示後有640k的大小限制。若是你給 pushState() 方法傳了一個序列化後大於640k的狀態對象,該方法會拋出異常。若是你須要更大的空間,建議使用 sessionStorage 以及 localStorage.
標題
Firefox 目前忽略這個參數,但將來可能會用到。在此處傳一個空字符串應該能夠安全的防範將來這個方法的更改。或者,你能夠爲跳轉的state傳遞一個短標題。
URL
新的歷史URL記錄。新URL沒必要須爲絕對路徑。若是新URL是相對路徑,那麼它將被做爲相對於當前URL處理。新URL必須與當前URL同源,不然 pushState() 會拋出一個異常。該參數是可選的,缺省爲當前URL。
注意: pushState() 絕對不會觸發 hashchange 事件,即便新的URL與舊的URL僅哈希不一樣也是如此。
不會當即加載頁面的狀況下改變了當前URL地址,並改變歷史記錄的當前條目,除非刷新頁面等操做
history.pushState(state, title , URL);
三個參數
state是一個JavaScript對象,popstate事件的state屬性包含該歷史記錄條目狀態對象的副本。
狀態對象能夠是能被序列化的任何東西。緣由在於Firefox將狀態對象保存在用戶的磁盤上,以便在用戶重啓瀏覽器時使用,咱們規定了狀態對象在序列化表示後有640k的大小限制。若是你給 pushState() 方法傳了一個序列化後大於640k的狀態對象,該方法會拋出異常。若是你須要更大的空間,建議使用 sessionStorage 以及 localStorage.
Firefox 目前忽略這個參數,但將來可能會用到。在此處傳一個空字符串應該能夠安全的防範將來這個方法的更改。或者,你能夠爲跳轉的state傳遞一個短標題。
新的歷史URL記錄。新URL沒必要須爲絕對路徑。若是新URL是相對路徑,那麼它將被做爲相對於當前URL處理。新URL必須與當前URL同源,不然 pushState() 會拋出一個異常。該參數是可選的,缺省爲當前URL。
注意: pushState() 絕對不會觸發 hashchange 事件,即便新的URL與舊的URL僅哈希不一樣也是如此。
不會當即加載頁面的狀況下改變了當前URL地址,並改變歷史記錄的當前條目,除非刷新頁面等操做
`history.replaceState(state, title , URL);`
複製代碼
每當活動的歷史記錄項發生變化時, popstate 事件都會被傳遞給window對象。若是當前活動的歷史記錄項是被 pushState 建立的,或者是由 replaceState 改變的,那麼 popstate 事件的狀態屬性 state 會包含一個當前歷史記錄狀態對象的拷貝。
window.onpopstate = function(event) {
alert("location: " + document.location + ", state: " + JSON.stringify(event.state));
};
//綁定事件處理函數.
history.pushState({page: 1}, "title 1", "?page=1"); //添加並激活一個歷史記錄條目 http://example.com/example.html?page=1,條目索引爲1
history.pushState({page: 2}, "title 2", "?page=2"); //添加並激活一個歷史記錄條目 http://example.com/example.html?page=2,條目索引爲2
history.replaceState({page: 3}, "title 3", "?page=3"); //修改當前激活的歷史記錄條目 http://ex..?page=2 變爲 http://ex..?page=3,條目索引爲3
history.back(); // 彈出 "location: http://example.com/example.html?page=1, state: {"page":1}"
history.back(); // 彈出 "location: http://example.com/example.html, state: null history.go(2); // 彈出 "location: http://example.com/example.html?page=3, state: {"page":3}
複製代碼
既然 history.pushState 和 history.replaceState 都不會觸發頁面的更新,咱們就須要手動給 window 對象添加 pushState 和 replaceState 事件,這個很重要!
const listenWrapper = function (type) {
const _func = history[type];
return function () {
console.log(this);
const func = _func.apply(this, arguments);
const e = new Event(type);
e.arguments = arguments;
window.dispatchEvent(e);
return func;
};
};
history.pushState = listenWrapper('pushState');
history.replaceState = listenWrapper('replaceState');
window.addEventListener('pushState', function (e) {
console.log(e)
});
複製代碼
1. 在pushState執行的時候建立自定義事件
2. 在pushSatate外部寫自定義事件的監聽事件
3. 在pushState執行的時候執行自定義事件
複製代碼
頁面加載時,或許會有個非null的狀態對象。這是有可能發生的,舉個例子,假如頁面(經過pushState() 或 replaceState() 方法)設置了狀態對象然後用戶重啓了瀏覽器。那麼當頁面從新加載時,頁面會接收一個onload事件,但沒有 popstate 事件。然而,假如你讀取了history.state屬性,你將會獲得如同popstate 被觸發時能獲得的狀態對象。
你能夠讀取當前歷史記錄項的狀態對象state,而沒必要等待popstate 事件
監聽點擊事件禁止默認跳轉操做,手動利用history實現一套跳轉邏輯,根據location.pathname渲染界面.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<meta http-equiv="X-UA-Compatible" content="ie=edge"/>
<title>Document</title>
</head>
<body>
<ul>
<li>
<a href="/a">a</a>
</li>
<li>
<a href="/b">b</a>
</li>
<li>
<a href="/c">c</a>
</li>
</ul>
<div id="view"></div>
<script>
var view = null;
// 頁面加載完不會觸發 hashchange,這裏主動觸發一次 hashchange 事件,該事件快於onLoad,因此須要在這裏操做
window.addEventListener('DOMContentLoaded', function () {
view = document.querySelector('#view');
document
.querySelectorAll('a[href]')
.forEach(e => e.addEventListener('click', function (_e) {
_e.preventDefault();
history.pushState(null, '', e.getAttribute('href'));
viewChange();
}));
viewChange();
});
// 監聽路由變化
window.addEventListener('popstate', viewChange);
// 渲染視圖
function viewChange() {
switch (location.pathname) {
case '/b':
view.innerHTML = 'b';
break;
case '/c':
view.innerHTML = 'c';
break;
default:
view.innerHTML = 'a';
break;
}
}
</script>
</body>
</html>
複製代碼
API
基本的路由方法:
router.push(url, onComplete)
router.replace(url, onComplete)
router.go(n)
router.back()
router.stop()
複製代碼
<!DOCTYPE html>
<html>
<head>
<title>router</title>
</head>
<body>
<ul>
<li onclick="router.push('/a', ()=>console.log('push a'))">push a</li>
<li onclick="router.push('/b', ()=>console.log('push b'))">push b</li>
<li onclick="router.replace('/c', ()=>console.log('replace c'))">replace c</li>
<li onclick="router.go(1)">go</li>
<li onclick="router.back(-1)">back</li>
<li onclick="router.stop()">stop</li>
</ul>
<div id="view"></div>
</body>
</html>
複製代碼
import Router from '../router'
window.router = new Router('view', {
routes: [
{
path: '/a',
component: '<p>a</p>'
},
{
path: '/b',
component: '<p>b</p>'
},
{
path: '/c',
component: '<p>c</p>'
},
{ path: '*', redirect: '/index' }
]
}, 'hash')// 或者'html5'
複製代碼
import HashHstory from "./HashHistory";
import Html5History from "./Html5History";
export default class Router {
constructor(wrapper, options, mode = 'hash') {
this._wrapper = document.querySelector(`#${wrapper}`)
if (!this._wrapper) {
throw new Error(`你須要提供一個容器元素插入`)
}
// 是否支持HTML5 History 模式
this._supportsReplaceState = window.history && typeof window.history.replaceState === 'function'
// 匹配路徑
this._cache = {}
// 默認路由
this._defaultRouter = options.routes[0].path
this.route(options.routes)
// 啓用模式
this._history = (mode !== 'hash' && this._supportsReplaceState) ? new Html5History(this, options) : new HashHstory(this, options)
}
// 添加路由
route(routes) {
routes.forEach(item => this._cache[item.path] = item.component)
}
// 原生瀏覽器前進
go(n = 1) {
window.history.go(n)
}
// 原生瀏覽器後退
back(n = -1) {
window.history.go(n)
}
// 增長
push(url, onComplete) {
this._history.push(url, onComplete)
}
// 替換
replace(url, onComplete) {
this._history.replace(url, onComplete)
}
// 移除事件
stop() {
this._history.stop()
}
}
複製代碼
export default class HashHistory {
constructor(router, options) {
this.router = router
this.onComplete = null
// 監聽事件
window.addEventListener('load', this.onChange)
window.addEventListener('hashchange', this.onChange)
}
onChange = () => {
// 匹配失敗重定向
if (!location.hash || !this.router._cache[location.hash.slice(1)]) {
window.location.hash = this.router._defaultRouter
} else {
// 渲染視圖
this.router._wrapper.innerHTML = this.router._cache[location.hash.slice(1)]
this.onComplete && this.onComplete() && (this.onComplete = null)
}
}
push(url, onComplete) {
window.location.hash = `${url}`
onComplete && (this.onComplete = onComplete)
}
replace(url, onComplete) {
// 優雅降級
if (this.router._supportsReplaceState) {
window.location.hash = `${url}`
window.history.replaceState(null, null, `${window.location.origin}#${url}`)
} else {
// 須要先看看當前URL是否已經有hash值
const href = location.href
const index = href.indexOf('#')
url = index > 0
? `${href.slice(0, index)}#${url}`
: `${href}#${url}`
// 域名不變的狀況下不會刷新頁面
window.location.replace(url)
}
onComplete && (this.onComplete = onComplete)
}
// 移除事件
stop() {
window.removeEventListener('load', this.onChange)
window.removeEventListener('hashchange', this.onChange)
}
}
複製代碼
export default class Html5Hstory {
constructor(router, options) {
this.addEvent()
this.router = router
this.onComplete = null
// 監聽事件
window.addEventListener('popstate', this.onChange)
window.addEventListener('load', this.onChange)
window.addEventListener('replaceState', this.onChange);
window.addEventListener('pushState', this.onChange);
}
// pushState/replaceState不會觸發popstate事件,因此咱們須要自定義
addEvent() {
const listenWrapper = function (type) {
const _func = history[type];
return function () {
const func = _func.apply(this, arguments);
const e = new Event(type);
e.arguments = arguments;
window.dispatchEvent(e);
return func;
};
};
history.pushState = listenWrapper('pushState');
history.replaceState = listenWrapper('replaceState');
}
onChange() {
// 匹配失敗重定向
if (location.pathname === '/' || !this.router._cache[location.pathname]) {
window.history.pushState(null, '', `${window.location.origin}${this.router._defaultRouter}`);
} else {
// 渲染視圖
this.router._wrapper.innerHTML = this.router._cache[location.pathname]
this.onComplete && this.onComplete() && (this.onComplete = null)
}
}
push(url, onComplete) {
window.history.pushState(null, '', `${window.location.origin}${url}`);
onComplete && (this.onComplete = onComplete)
}
replace(url, onComplete) {
window.history.replaceState(null, null, `${window.location.origin}${url}`)
onComplete && (this.onComplete = onComplete)
}
// 移除事件
stop() {
window.removeEventListener('load', this.onChange)
window.removeEventListener('popstate', this.onChange)
window.removeEventListener('replaceState', this.onChange)
window.removeEventListener('pushState', this.onChange)
}
}
複製代碼
以上是全部內容了