1渣叛、前端路由
在 Web 前端單頁應用 SPA(Single Page Application)中,路由描述的是 URL 與 UI 之間的映射關
系,這種映射是單向的特幔,即 URL 變化引起 UI 更新(無需刷新頁面)。
vue-router 是 Vue.js 官方的路由插件闸昨,它和 vue.js 是深度集成的薄风,適合用于構建單頁面應用。
那與傳統(tǒng)的頁面跳轉有什么區(qū)別呢撇他?
- vue 的單頁面應用是基于路由和組件的逆粹,路由用于設定訪問路徑僻弹,并將路徑和組件映射起來蹋绽。
- 傳統(tǒng)的頁面應用,是用一些超鏈接來實現(xiàn)頁面切換和跳轉的蚣抗。
在 vue-router 單頁面應用中翰铡,則是路徑之間的切換,也就是組件的切換迷捧。路由模塊的本質(zhì)就是建立起 url 和頁面之間的映射關系漠秋。
2庆锦、包含的功能
Vue Router 包含的功能有:
- 嵌套的路由/視圖表
- 模塊化的肥荔、基于組件的路由配置
- 路由參數(shù)中符、查詢淀散、通配符
- 基于 Vue.js 過渡系統(tǒng)的視圖過渡效果
- 細粒度的導航控制
- 帶有自動激活的 CSS class 的鏈接
- HTML5 歷史模式或 hash 模式,在 IE9 中自動降級
- 自定義的滾動條行為
3郭膛、vue-router 實現(xiàn)原理
在 vue-router 中,可以通過三種方式來實現(xiàn)前端路由的變化如捅,分別為 hash己肮、history 和 abstract谎僻。
3.1 hash
hash 是 URL 中 hash (#) 及后面的那部分戈稿,常用作錨點在頁面內(nèi)進行導航,最重要的是改變 URL 中的 hash 部分不會引起頁面刷新跳昼。
我們可以通過 hashchange 事件監(jiān)聽 URL 的變化鹅颊,改變 URL 的方式只有這幾種:
- 通過瀏覽器前進后退改變 URL
- 通過
<a>
標簽改變 URL - 通過
window.location
改變 URL
我們通過下面的例子锚烦,來看下 hash 實現(xiàn)路由的原理:
<!DOCTYPE html>
<html lang="en">
<body>
<ul>
<ul>
<!-- 定義路由 -->
<li><a href="#/home">home</a></li>
<li><a href="#/about">about</a></li>
<!-- 渲染路由對應的 UI -->
<div id="routeView"></div>
</ul>
</ul>
</body>
<script>
let routerView = document.getElementById('routeView');
window.addEventListener('hashchange', () => {
routerView.innerHTML = location.hash;
});
window.addEventListener('DOMContentLoaded', () => {
if (!location.hash) {
//如果不存在hash值,那么重定向到#/
location.hash = '/';
} else {
//如果存在hash值彻亲,那就渲染對應UI
routerView.innerHTML = location.hash;
}
});
</script>
</html>
上面的代碼很簡單,只要注意一下幾點:
- 我們通過 a 標簽的 href 屬性來改變 URL 的 hash 值宙址。當然,觸發(fā)瀏覽器的前進后退按鈕也可以舀患,或者在控制臺輸入 window.location 賦值來改變 hash 都是可以的聊浅。也都會觸發(fā) hashchange。
- 我們監(jiān)聽 hashchange 事件。一旦事件觸發(fā)售碳,就改變 routerView 的內(nèi)容间景,若是在 vue 中,這改變的應當是 router-view 這個組件的內(nèi)容封拧。
- 為何又監(jiān)聽了 load 事件?這時因為頁面第一次加載完不會觸發(fā) hashchange曹铃,因而用 load 事件來監(jiān)聽 hash 值铛只,再將視圖渲染成對應的內(nèi)容淳玩。
3.2 history
由于 html5 標準的發(fā)布,history 的 api 增加了兩個 API非竿。pushState 和 replaceState蜕着。通過這兩個 API 可以改變 url 地址且不會發(fā)送請求。同時還有 popstate 事件红柱。通過這些就能用另一種方式來實現(xiàn)前端路由了承匣,但原理都是跟 hash 實現(xiàn)相同的。
用了 HTML5 的實現(xiàn)锤悄,單頁路由的 url 就不會多出一個#韧骗,變得更加美觀隶症。但因為沒有 # 號胁住,所以當用戶刷新頁面之類的操作時,瀏覽器還是會給服務器發(fā)送請求笋婿。為了避免出現(xiàn)這種情況,所以這個實現(xiàn)需要服務器的支持,需要把所有路由都重定向到根頁面嗅虏。
我們主要說幾個注意點:
- 通過 pushState/replaceState 或
<a>
標簽改變 URL 不會觸發(fā)頁面刷新,也不會觸發(fā) popstate 方法谆刨。所以我們可以攔截 pushState/replaceState 的調(diào)用和<a>
標簽的點擊事件來檢測 URL 變化她我,從而觸發(fā) router-view 的視圖更新。 - 通過瀏覽器前進后退改變 URL 芽偏,或者通過 js 調(diào)用 history 的 back捂齐,go,forward 方法贵少,都會觸發(fā) popstate 事件录平,所以我們可以監(jiān)聽 popstate 來觸發(fā) router-view 的視圖更新绪氛。
所以涝影,我們其實是需要監(jiān)聽 popstate 以及攔截 pushState/placeState 以及 a 的點擊去實現(xiàn)監(jiān)聽 URL 的變化握童。
我們通過下面的例子,來看下 history 實現(xiàn)路由的原理:
<!DOCTYPE html>
<html lang="en">
<body>
<ul>
<ul>
<li><a href="/home">home</a></li>
<li><a href="/about">about</a></li>
<div id="routeView"></div>
</ul>
</ul>
</body>
<script>
let routerView = document.getElementById('routeView');
window.addEventListener('DOMContentLoaded', () => {
routerView.innerHTML = location.pathname;
var linkList = document.querySelectorAll('a[href]');
linkList.forEach(el =>
el.addEventListener('click', function(e) {
e.preventDefault();
history.pushState(null, '', el.getAttribute('href'));
routerView.innerHTML = location.pathname;
}),
);
});
window.addEventListener('popstate', () => {
routerView.innerHTML = location.pathname;
});
</script>
</html>
注意一下幾個重點:
- 我們監(jiān)聽 popState 事件定拟。一旦事件觸發(fā)(例如觸發(fā)瀏覽器的前進后端按鈕于微,或者在控制臺輸入 history逗嫡,go,back株依,forward 賦值)驱证,就改變 routerView 的內(nèi)容。
- 我們通過 a 標簽的 href 屬性來改變 URL 的 path 值恋腕。這里需要注意的就是抹锄,當改變 path 值時,默認會觸發(fā)頁面的跳轉荠藤,所以需要攔截
<a>
標簽點擊事件的默認行為伙单,這樣就阻止了 a 標簽自動跳轉的行為, 點擊時使用 pushState 修改 URL 并更新手動 UI哈肖,從而實現(xiàn)點擊鏈接更新 URL 和 UI 的效果吻育。
3.3 abstract
abstract 是 vue 路由中的第三種模式,支持所有 JavaScript 運行環(huán)境淤井,如 Node.js 服務器端布疼。如果發(fā)現(xiàn)沒有瀏覽器的 API,路由會自動強制進入這個模式币狠。
4游两、使用 vue-router
首先,我們當然是通過 vue-cli 去創(chuàng)建一個腳手架总寻,這一步比較簡單器罐,就直接省略了。然后渐行,我們修改
App.vue:
<template>
<div id="app">
<div id="nav">
<router-link to="/home">Home</router-link> |
<router-link to="/about">About</router-link>
</div>
<router-view />
</div>
</template>
router/index.js
import Vue from 'vue';
import VueRouter from 'vue-router';
import Home from '../views/Home.vue';
import About from '../views/About.vue';
Vue.use(VueRouter);
const routes = [
{
path: '/home',
name: 'Home',
component: Home,
},
{
path: '/about',
name: 'About',
component: About,
},
];
const router = new VueRouter({
mode: 'history',
routes,
});
export default router;
至于 home 組件和 about 組件轰坊,就是非常簡單的一句話,就不放出來了祟印。
到這里最基本的使用 vue-router 就完成了肴沫。
再將 VueRouter 引入改成我們的 myVueRouter.js
import VueRouter from './myVueRouter'; //修改代碼
5、剖析 VueRouter 本質(zhì)
我們在仔細看看 vue 是如何引入 vue-router 的蕴忆。
- 首先颤芬,我們通過
import VueRouter from 'vue-router'
引入 vue-router。 - 然后套鹅,通過
Vue.use(VueRouter)
去觸發(fā)了 vue-router 中的 install 方法站蝠。 - 最后,
const router = new VueRouter({...})
, 再把 router 作為參數(shù)的一個屬性值卓鹿,new Vue({router})
复局。
我們看看這三步氯葬,特別是最后一步我們是 new 出來的一個 vue-router 的實例喜最,所以,我們可以預見聚蝶,VueRouter 應該是一個類。所以我們這樣寫:
class VueRouter {}
然后通過 Vue.use() 我們知道藻治,VueRouter 應該有個 install 方法碘勉,并且第一個參數(shù)應該是 Vue 實例。
let Vue = null;
class VueRouter {}
VueRouter.install = v => {
Vue = v;
};
// 然后導出
export default VueRouter;
5.1 加載組件
vue-router 還自帶了兩個組件桩卵,分別是 router-link 和 router-view
(1)router-link
組件支持用戶在具有路由功能的應用中 (點擊) 導航验靡。 通過 to 屬性指定目標地址,默認渲染成帶有正確鏈接的 <a>
標簽
使用 router-link 理由如下:
- 無論是 HTML5 history 模式還是 hash 模式吸占,它的表現(xiàn)行為一致晴叨,所以,當你要切換路由模式矾屯,或者在 IE9 降級使用 hash 模式,無須作任何變動初厚。
- 在 HTML5 history 模式下件蚕,router-link 會守衛(wèi)點擊事件,讓瀏覽器不再重新加載頁面产禾。
- 當你在 HTML5 history 模式下使用 base 選項之后排作,所有的 to 屬性都不需要寫 (基路徑) 了。
(2)router-view
組件是一個 functional 組件亚情,渲染路徑匹配到的視圖組件妄痪。還可以內(nèi)嵌自己,根據(jù)嵌套路徑楞件,渲染嵌套組件衫生。
(3)加載 router-link 和 router-view:
let Vue = null;
class VueRouter {}
VueRouter.install = v => {
Vue = v;
// 新增代碼
Vue.component('router-link', {
render(h) {
return h('a', {}, 'home');
},
});
Vue.component('router-view', {
render(h) {
return h('div', {}, 'home視圖');
},
});
};
export default VueRouter;
這個時候,我們的腳手架應該可以跑起來了土浸,并且顯示 home 視圖罪针。
5.2 加載 $router
Vue.install 中除了加載 router-link 和 router-view 這兩個組件以外,還有很重要的一個功能黄伊。那就是加載 $router
和 $route
泪酱。
$route
和 $router
有什么區(qū)別?
$router
是 VueRouter 的實例對象还最,$route
是當前路由對象墓阀,也就是說 $route
是 $router
的一個屬性。
其實 $router
中有一個 history 屬性拓轻,它代表了 hashHistory 還是 HTML5history 實例對象斯撮,然后這個對象里面有個 current 屬性,這個屬性對象就是 $route
悦即。
通過 main.js 中的 new Vue({router})
吮成,我們可以把 router 實例(也就是剛剛 new 出來的)掛載到根組件的 $options
中橱乱。可是我們發(fā)現(xiàn)粱甫,我們在每個實例組件泳叠,都可以通過 this.$router
訪問到 router 實例。而這個茶宵,就是在 install 中實現(xiàn)的危纫。
// myVueRouter.js
let Vue = null;
class VueRouter {}
VueRouter.install = function(v) {
Vue = v;
// 新增代碼
Vue.mixin({
beforeCreate() {
if (this.$options && this.$options.router) {
// 如果是根組件
this._root = this; //把當前實例掛載到_root上
this._router = this.$options.router;
} else {
//如果是子組件
this._root = this.$parent && this.$parent._root;
}
// 定義 $router
Object.defineProperty(this, '$router', {
get() {
return this._root._router;
},
});
},
});
Vue.component('router-link', {
render(h) {
return h('a', {}, '首頁');
},
});
Vue.component('router-view', {
render(h) {
return h('h1', {}, '首頁視圖');
},
});
};
解釋一下上面的代碼:
- 首先,mixin 的作用是將 mixin 的內(nèi)容混合到 Vue 的初始參數(shù) options 中乌庶。
- 為什么是 beforeCreate 而不是 created 呢种蝶?因為如果是在 created 操作的話,$options 已經(jīng)初始化好了瞒大。
- 在 beforeCreate 中螃征,我們判斷如果是根組件,我們將傳入的實例和 router 分別綁定到 _root 和 _router 上透敌。
- 如果是子組件盯滚,我們就去遞歸讀取到根組件,綁定到 _root 上酗电。
- 我們?yōu)?vue 的原型對象魄藕,定義 $router,然后返回值是 _root(根組件)的 _router撵术。
那么背率,我們還有一個問題,為什么判斷當前組件是子組件嫩与,就可以直接從父組件拿到 _root 根組件呢寝姿?
這就需要我們了解父子組件的渲染順尋了,其實很簡單蕴纳,直接列出來了:
父 beforeCreate -> 父 created -> 父 beforeMounte ->
子 beforeCreate -> 子 create -> 子 beforeMount ->子 mounted ->
父 mounted
可以看到会油,在執(zhí)行子組件的 beforeCreate 的時候,父組件已經(jīng)執(zhí)行完 beforeCreate 了古毛,那理所當然父組件已經(jīng)有 _root 了翻翩。
5.3 完善構造器
首先,我們看看稻薇,我們在 new VueRouter 的時候嫂冻,傳入了什么參數(shù):
const router = new VueRouter({
mode: 'history',
routes,
});
可以看到,暫時傳入了兩個塞椎,一個是 mode桨仿,還有一個是 routes 數(shù)組。因此案狠,我們可以這樣實現(xiàn)構造器服傍。
class VueRouter {
constructor(options) {
this.mode = options.mode || 'hash';
this.routes = options.routes || []; // 你傳遞的這個路由是一個數(shù)組表
}
}
由于直接處理數(shù)組比較不方便钱雷,所以我們做一次轉換,采用 path 為 key吹零,component 為 value 的方式罩抗。
class VueRouter {
constructor(options) {
this.mode = options.mode || 'hash';
this.routes = options.routes || [];
// 新增代碼
this.routesMap = this.changeMap(this.routes);
}
// 新增代碼
createMap(routes) {
return routes.reduce((pre, current) => {
pre[current.path] = current.component;
return pre;
}, {});
}
}
接下來,我們還需要在 vue-router 的實例中保存當前路徑(在包含一些例如 params 信息灿椅,其實就是 $route)套蒂,所以我們?yōu)榱朔奖愎芾恚褂靡粋€對象來表示:
class HistoryRoute {
constructor() {
this.current = null;
}
}
class VueRouter {
constructor(options) {
this.mode = options.mode || 'hash';
this.routes = options.routes || [];
this.routesMap = this.changeMap(this.routes);
// 新增代碼
this.history = new HistoryRoute();
}
createMap(routes) {
return routes.reduce((pre, current) => {
pre[current.path] = current.component;
return pre;
}, {});
}
}
這個時候茫蛹,我們的 history 對象中操刀,就保存了關于當前路徑的屬性 current。
只是婴洼,我們這個時候的 current 還是 null骨坑,所以,我們需要做初始化操作柬采。
在初始化的時候卡啰,我們需要判斷當前是什么模式,然后將當前路徑保存到 current 中警没。
//myVueRouter.js
let Vue = null;
class HistoryRoute {
constructor() {
this.current = null;
}
}
class VueRouter {
constructor(options) {
this.mode = options.mode || 'hash';
this.routes = options.routes || [];
this.routesMap = this.createMap(this.routes);
this.history = new HistoryRoute();
// 新增代碼;
this.init();
}
// 新增代碼;
init() {
if (this.mode === 'hash') {
// 先判斷用戶打開時有沒有 hash 值,沒有的話跳轉到 #/
location.hash ? '' : (location.hash = '/');
window.addEventListener('load', () => {
this.history.current = location.hash.slice(1);
});
window.addEventListener('hashchange', () => {
this.history.current = location.hash.slice(1);
});
} else {
location.pathname ? '' : (location.pathname = '/');
window.addEventListener('load', () => {
this.history.current = location.pathname;
});
window.addEventListener('popstate', () => {
this.history.current = location.pathname;
});
}
}
createMap(routes) {
return routes.reduce((pre, current) => {
pre[current.path] = current.component;
return pre;
}, {});
}
}
5.4 完善 $route
現(xiàn)在振湾,我們已經(jīng)在構造器中根據(jù)不同模式杀迹,將路徑賦值給 history 的 current,那么押搪,我們現(xiàn)在就可以給 $route 賦值了树酪,當然 this.$route 也就是 current。
VueRouter.install = function(v) {
Vue = v;
Vue.mixin({
beforeCreate() {
if (this.$options && this.$options.router) {
// 如果是根組件
this._root = this; //把當前實例掛載到_root上
this._router = this.$options.router;
} else {
//如果是子組件
this._root = this.$parent && this.$parent._root;
}
Object.defineProperty(this, '$router', {
get() {
return this._root._router;
},
});
// 新增代碼;
Object.defineProperty(this, '$route', {
get() {
return this._root._router.history.current;
},
});
},
});
Vue.component('router-link', {
render(h) {
return h('a', {}, '首頁');
},
});
Vue.component('router-view', {
render(h) {
return h('h1', {}, '首頁視圖');
},
});
};
代碼極其簡單大州,就是在 vue 的原型鏈上再次定義了一個 $route续语,指向 current 即可。
5.5 完善 router-view
我們已經(jīng)通過 current 保存到了當前的路徑厦画,那么我們現(xiàn)在就可以根據(jù)當前的路徑疮茄,拿到對應的 component,然后根暑,將這個 component 渲染出來就好了力试。
Vue.component('router-view', {
render(h) {
// 新增代碼
let current = this._self._root._router.history.current;
let routeMap = this._self._root._router.routesMap;
return h(routeMap[current]);
},
});
但是這個時候,我們頁面是渲染不出來的排嫌,原因在于畸裳,current 并不是響應式的,我們拿到的 current 是最初的淳地,也就是 null怖糊。所以帅容,我們需要把 current 變成響應式,只要路徑變化伍伤,我們的 router-view 就必須跟著變化并徘。
我們需要用到 Vue 中的一個工具方法,這個方法很簡單嚷缭,就是把變量變成響應式饮亏。那么在什么時候去把 current 變成響應式呢,其實很多地方都可以阅爽,例如在第一次定義 history 的時候路幸,或者在根組件渲染出來 $router 的時候,我們就在第一次定義 history 的時候去改變付翁。
constructor(options) {
this.mode = options.mode || 'hash';
this.routes = options.routes || [];
this.routesMap = this.changeMap(this.routes);
// this.history = new HistoryRoute();
// 新增代碼
// 將history變成響應式的數(shù)據(jù)
Vue.util.defineReactive(this,"history",new HistoryRoute());
this.init();
};
5.6 完善 router-link
首先简肴,我們應該比較清楚 router-link 的使用。
<router-link to="/home">Home</router-link>
<router-link to="/about">About</router-link>
很顯然百侧,父組件間 to 這個路徑傳進去砰识,子組件接收。所以我們實現(xiàn)代碼如下:
Vue.component('router-link', {
props: {
to: String,
},
render(h) {
// 新增代碼
let mode = this._self._root._router.mode;
let to = mode === 'hash' ? '#' + this.to : this.to;
return h('a', { attrs: { href: to } }, this.$slots.default);
},
});
這個時候佣渴,我們把 router-link 渲染成了 a 標簽辫狼,到這里,你們腳手架應該點擊 router-link 就能切換相應的組件了辛润。
Vue.mixin({
beforeCreate() {
if (this.$options && this.$options.router) {
// 如果是根組件
this._root = this; //把當前實例掛載到_root上
this._router = this.$options.router;
// 新增代碼
// 攔截 router-link
this._router.mode === 'history' &&
document.addEventListener('click', e => {
if (e.target.className === 'router-link-to') {
// 阻止默認跳轉事件;
e.preventDefault();
// 手動改變url路徑
history.pushState(null, '', e.target.getAttribute('href'));
// 為current賦值url路徑
this._router.history.current = location.pathname;
}
});
} else {
//如果是子組件
this._root = this.$parent && this.$parent._root;
}
},
});
上面代碼攔截比較簡單膨处,解釋一下:
- 判斷如果是 router-link 的 a 標簽,并且是 history 模式砂竖,那么就阻止默認跳轉事件真椿。
- 通過 history.pushState 方法去手動的改變 url 的路徑,這個方法改變 url 不會刷新頁面乎澄,這個很重要突硝。
- 當然這樣改變 url 也不會觸發(fā) popstate 方法,所以我們手動給 current 賦值置济。
- 因為我們的 history 已經(jīng)是動態(tài)響應的了解恰,所以很自然,這個時候 router-view 里面的組件也就更新了舟肉。
到此修噪,我們對于 vue-router 的實現(xiàn)基本完成了,附上全部的 vue-router 的代碼:
//myVueRouter.js
let Vue = null;
class HistoryRoute {
constructor() {
this.current = null;
}
}
class VueRouter {
constructor(options) {
this.mode = options.mode || "hash";
this.routes = options.routes || [];
this.routesMap = this.createMap(this.routes);
// this.history = new HistoryRoute();
Vue.util.defineReactive(this, "history", new HistoryRoute());
this.init();
}
createMap(routes) {
return routes.reduce((pre, current) => {
pre[current.path] = current.component;
return pre;
}, {});
}
init() {
if (this.mode === "hash") {
// 先判斷用戶打開時有沒有 hash 值路媚,沒有的話跳轉到 #/
location.hash ? "" : (location.hash = "/");
window.addEventListener("load", () => {
this.history.current = location.hash.slice(1);
});
window.addEventListener("hashchange", () => {
this.history.current = location.hash.slice(1);
});
} else {
location.pathname ? "" : (location.pathname = "/");
window.addEventListener("load", () => {
this.history.current = location.pathname;
});
window.addEventListener("popstate", () => {
this.history.current = location.pathname;
});
}
}
}
VueRouter.install = (v) => {
Vue = v;
Vue.mixin({
beforeCreate() {
if (this.$options && this.$options.router) {
this._root = this;
this._router = this.$options.router;
this._router.mode === "history" &&
document.addEventListener("click", (e) => {
if (e.target.className === "router-link-to") {
// 阻止默認跳轉事件;
e.preventDefault();
// 手動改變url路徑
history.pushState(null, "", e.target.getAttribute("href"));
// 為current賦值url路徑
this._router.history.current = location.pathname;
}
});
} else {
this._root = this.$parent && this.$parent._root;
}
Object.defineProperty(this, "$router", {
get() {
return this._root._router;
},
});
Object.defineProperty(this, "$route", {
get() {
return this._root._router.history.current;
},
});
},
});
Vue.component("router-link", {
props: {
to: String,
},
render(h) {
let mode = this._self._root._router.mode;
let to = mode === "hash" ? "#" + this.to : this.to;
return h(
"a",
{ attrs: { href: to }, class: "router-link-to" },
this.$slots.default
);
},
});
Vue.component("router-view", {
render(h) {
let current = this._self._root._router.history.current;
let routeMap = this._self._root._router.routesMap;
return h(routeMap[current]);
},
});
};
export default VueRouter;
6黄琼、總結
在 main.js 中導入,使用 Vue.use() 方法注冊組件,然后就會調(diào)用 install() 方法脏款,并構造一個 VueRouter 實例導出围苫。
在 install() 方法中:
- 注冊了兩個子組件:router-link 和 router-view;
- 調(diào)用 Vue.mixin() 方法混入 beforeCreate() 方法撤师,然后判斷是否為根組件剂府,如果是則掛載 this 到 this._root 上并且掛 _router,否則將根組件掛載到子組件的 _root 上剃盾;
- 調(diào)用 Object.defineProperty 將 $router 和 $route 綁定到 Vue.prototype 上腺占,
$router = this._root._router
,$route = this._root._router.history.current
痒谴。
- 在 VueRouter 實例中:
- 進入 constructor(options) 實例化并傳入 options 參數(shù)衰伯,得到 this.mode 和 this.routes,再將 this.routes 數(shù)組進行 map 化處理积蔚;
- 然后調(diào)用 Vue.util.defineReactive 將 this.history 變成響應式意鲸,其值為 HistoryRoute 的實例,實例中定義了一個 this.current 變量尽爆;
- 再然后調(diào)用 init() 方法進行初始化怎顾,判斷 this.mode 等于 hash 則監(jiān)聽 load 和 hashChange 事件,得到 location.hash 并賦值給 current漱贱,否則判斷 this.mode 等于 history 則監(jiān)聽 load 和 popstate 事件槐雾,得到 loaction.pathname 并賦值給 current。
7幅狮、參考
https://juejin.cn/post/6854573222231605256