路由这概念最开始是在后端出现的,在之前先后端不分离的时候,由后端来控制路由,服务器接收客户端的请求,解析对应的url路径,并返回对应的页面/资源。javascript
简单的说 路由就是根据不一样的url地址来展现不一样的内容或页面.html
在好久好久之前~ 用户的每次更新操做都须要从新刷新页面,很是的影响交互体验,后来,为了解决这个问题,便有了Ajax(异步加载方案),Ajax给体验带来了极大的提高。前端
虽然Ajax解决了用户交互时体验的痛点,可是多页面之间的跳转同样会有很差的体验,因此便有了spa(single-page application)使用的诞生。而spa应用即是基于前端路由实现的,因此便有了前端路由。vue
现在比较火的vue-router/react-router 也是基于前端路由的原理实现的~java
window对象提供了onhashchange事件来监听hash值的改变,一旦url中的hash值发生改变,便会触发该事件。react
window.onhashchange = function(){
// hash 值改变
// do you want
}
复制代码
HTML5的History API 为浏览器的全局history对象增长的扩展方法。webpack
简单来讲,history其实就是浏览器历史栈的一个接口。这里不细说history的每一个API啦。具体可查阅 传送门git
window对象提供了onpopstate事件来监听历史栈的改变,一旦历史栈信息发生改变,便会触发该事件。github
须要特别注意的是,调用history.pushState()或history.replaceState()不会触发popstate事件。只有在作出浏览器动做时,才会触发该事件。web
window.onpopstate = function(){
// 历史栈 信息改变
// do you want
}
复制代码
history提供了两个操做历史栈的API:history.pushState 和 history.replaceState
history.pushState(data[,title][,url]);//向历史记录中追加一条记录
复制代码
history.replaceState(data[,title][,url]);//替换当前页在历史记录中的信息。
复制代码
// data: 一个JavaScript对象,与用pushState()方法建立的新历史记录条目关联。不管什么时候用户导航到新建立的状态,popstate事件都会被触发,而且事件对象的state属性都包含历史记录条目的状态对象的拷贝。
//title: FireFox浏览器目前会忽略该参数,虽然之后可能会用上。考虑到将来可能会对该方法进行修改,传一个空字符串会比较安全。或者,你也能够传入一个简短的标题,标明将要进入的状态。
//url: 新的历史记录条目的地址。浏览器不会在调用pushState()方法后加载该地址,但以后,可能会试图加载,例如用户重启浏览器。新的URL不必定是绝对路径;若是是相对路径,它将以当前URL为基准;传入的URL与当前URL应该是同源的,不然,pushState()会抛出异常。该参数是可选的;不指定的话则为文档当前URL。
复制代码
对比 | Hash | History |
---|---|---|
观赏性 | 丑 | 美 |
兼容性 | >ie8 | >ie10 |
实用性 | 直接使用 | 需后端配合 |
命名空间 | 同一document | 同源 |
本demo只是想说帮助咱们经过实践更进一步的理解前端路由这个概念,因此只作了简单的实现~
当咱们使用history模式时,若是没有进行配置,刷新页面会出现404。
缘由是由于history模式的url是真实的url,服务器会对url的文件路径进行资源查找,找不到资源就会返回404。
这个问题的解决方案这里就不细说了,google一下,你就知道~ 咱们在如下demo使用webpack-dev-server的里的historyApiFallback属性来支持HTML5 History Mode。
|-- package.json
|-- webpack.config.js
|-- index.html
|-- src
|-- index.js
|-- routeList.js
|-- base.js
|-- hash.js
|-- history.js
复制代码
废话很少说,直接上代码~
package.json
{
"name": "web_router",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"dev": "webpack-dev-server --config ./webpack.config.js"
},
"author": "webfansplz",
"license": "MIT",
"devDependencies": {
"html-webpack-plugin": "^3.2.0",
"webpack": "^4.28.1",
"webpack-cli": "^3.2.1",
"webpack-dev-server": "^3.1.14"
}
}
复制代码
webpack.config.js
'use strict';
const path = require('path');
const webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
mode: 'development',
entry: './src/index.js',
output: {
filename: '[name].js'
},
devServer: {
clientLogLevel: 'warning',
hot: true,
inline: true,
open: true,
//在开发单页应用时很是有用,它依赖于HTML5 history API,若是设置为true,全部的跳转将指向index.html (解决histroy mode 404)
historyApiFallback: true,
host: 'localhost',
port: '6789',
compress: true
},
plugins: [
new webpack.HotModuleReplacementPlugin(),
new HtmlWebpackPlugin({
filename: 'index.html',
template: 'index.html',
inject: true
})
]
};
复制代码
首先咱们先初始化定义咱们须要实现的功能及配置参数。
前端路由 | 参数 | 方法 |
---|---|---|
- | 模式(mode) | push(压入) |
- | 路由列表(routeList) | replace(替换) |
- | - | go(前进/后退) |
const MODE='';
const ROUTELIST=[];
class WebRouter {
constructor() {
}
push(path) {
...
}
replace(path) {
...
}
go(num) {
...
}
}
new WebRouter({
mode: MODE,
routeList: ROUTELIST
});
复制代码
前面咱们说了前端路由有两种实现方式。
1.定义路由列表
2.咱们分别为这两种方式建立对应的类,并根据不一样的mode参数进行实例化,完成webRouter类的实现。
export const ROUTELIST = [
{
path: '/',
name: 'index',
component: 'This is index page'
},
{
path: '/hash',
name: 'hash',
component: 'This is hash page'
},
{
path: '/history',
name: 'history',
component: 'This is history page'
},
{
path: '*',
name: 'notFound',
component: '404 NOT FOUND'
}
];
复制代码
export class HashRouter{
}
复制代码
export class HistoryRouter{
}
复制代码
import { HashRouter } from './hash';
import { HistoryRouter } from './history';
import { ROUTELIST } from './routeList';
//路由模式
const MODE = 'hash';
class WebRouter {
constructor({ mode = 'hash', routeList }) {
this.router = mode === 'hash' ? new HashRouter(routeList) : new HistoryRouter(routeList);
}
push(path) {
this.router.push(path);
}
replace(path) {
this.router.replace(path);
}
go(num) {
this.router.go(num);
}
}
const webRouter = new WebRouter({
mode: MODE,
routeList: ROUTELIST
});
复制代码
前面咱们已经实现了webRouter的功能,接下来咱们来实现两种方式。
由于两种模式都须要调用一个方法来实现不一样路由内容的刷新,so~
<!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>前端路由</title>
</head>
<body>
<div id="page"></div>
</body>
</html>
复制代码
const ELEMENT = document.querySelector('#page');
export class BaseRouter {
//list = 路由列表
constructor(list) {
this.list = list;
}
render(state) {
//匹配当前的路由,匹配不到则使用404配置内容 并渲染~
let ele = this.list.find(ele => ele.path === state);
ele = ele ? ele : this.list.find(ele => ele.path === '*');
ELEMENT.innerText = ele.component;
}
}
复制代码
ok,下面咱们来实现两种模式。
Hash模式
import { BaseRouter } from './base.js';
export class HashRouter extends BaseRouter {
constructor(list) {
super(list);
this.handler();
//监听hash变化事件,hash变化从新渲染
window.addEventListener('hashchange', e => {
this.handler();
});
}
//渲染
handler() {
this.render(this.getState());
}
//获取当前hash
getState() {
const hash = window.location.hash;
return hash ? hash.slice(1) : '/';
}
//获取完整url
getUrl(path) {
const href = window.location.href;
const i = href.indexOf('#');
const base = i >= 0 ? href.slice(0, i) : href;
return `${base}#${path}`;
}
//改变hash值 实现压入 功能
push(path) {
window.location.hash = path;
}
//使用location.replace实现替换 功能
replace(path) {
window.location.replace(this.getUrl(path));
}
//这里使用history模式的go方法进行模拟 前进/后退 功能
go(n) {
window.history.go(n);
}
}
复制代码
History模式
import { BaseRouter } from './base.js';
export class HistoryRouter extends BaseRouter {
constructor(list) {
super(list);
this.handler();
//监听历史栈信息变化,变化时从新渲染
window.addEventListener('popstate', e => {
this.handler();
});
}
//渲染
handler() {
this.render(this.getState());
}
//获取路由路径
getState() {
const path = window.location.pathname;
return path ? path : '/';
}
//使用pushState方法实现压入功能
//PushState不会触发popstate事件,因此须要手动调用渲染函数
push(path) {
history.pushState(null, null, path);
this.handler();
}
//使用replaceState实现替换功能
//replaceState不会触发popstate事件,因此须要手动调用渲染函数
replace(path) {
history.replaceState(null, null, path);
this.handler();
}
go(n) {
window.history.go(n);
}
}
复制代码
就这样,一个简单的前端路由就完成拉。
若是以为有帮助到你的话,给个star哈~