咱们可使用下面三种方法,来修改浏览器的地址javascript
修改如下location
对象的属性值,会致使当前页面从新加载vue
// 假如当前url为:https://www.example.com/ // 把url修改成:https://www.example.com/?t=example location.search = '?t=example'; // 把url修改成:https://example.com/?t=example location.hostname = 'example.com'; // 把url修改成:https://www.example.com/example location.pathname = 'example'; // 把url修改成:https://www.example.com:8080 location.port = 8080
修改hash
时,浏览器历史中会新增长一条记录,可是并不会刷新页面。所以SPA应用中,hash
也是一种切换路由的方式。java
// 假如当前url为:https://www.example.com/ // 把url修改成:https://www.example.com/#example location.hash = '#example';
使用location.replace(url)
方法跳转的url,并不会增长历史记录。react
使用location.reload()
方法能够从新加载当前页面,是否传参的区别以下:ios
location.reload(); // 从新加载,多是从缓存加载 location.reload(true); // 从新加载,从服务器加载
使用go(n)
能够在用户记录中沿任何方向导航(便可之前进也能够后退)。正值表示在历史中前进,负值表示在历史中后退。git
假如要前进1页,那么可使用window.history.
`go(1)。同时,也可使用
window.history.forward()`来作相同的事情。
假如要后退1页,那么可使用window.history.
`go(-1)。同时,也可使用
window.history.back()`来作相同的事情。github
若是使用window.history.go(0)
或window.history.go()
都会从新加载当前页面。正则表达式
上面咱们说到了,修改hash
能够作到改变页面的地址,在浏览器历史中添加一条记录,可是不会从新加载页面。vue-router
咱们同时能够配合hashchange
事件,监听页面地址hash
的变化。npm
使用history.pushState()
,相似是执行了location.href = url
,可是并不会从新加载页面。假如用户执行了后退操做,将会触发popstate
事件。
使用history.replaceState()
,相似是执行了location.replace(url)
,可是并不会从新加载页面。
注意,执行pushState、replaceState方法后,虽然浏览器地址有改变,可是并不会触发popState事件
首先咱们须要肯定的是,咱们的路由应该须要下面4个属性:
routes
:一个数组,包含全部注册的路由对象mode
: 路由的模式,能够选择hash
或history
base
:根路径constructor
:初始化新的路由实例class MiniRouter { constructor(options) { const { mode, routes, base } = options; this.mode = mode || (window.history.pushState ? 'history' : 'hash'); this.routes = routes || []; this.base = base || '/'; } } export default MiniRouter;
路由对象中包含下面两个属性
path
:由正则表达式表明的路径地址(并非字符串,后面会详细解释)cb
:路由跳转后执行的回调函数class MiniRouter { constructor(options) { const { mode, routes, base } = options; this.mode = mode || (window.history.pushState ? 'history' : 'hash'); this.routes = routes || []; this.base = base || '/'; } // 添加路由对象 👇 新增代码 // routerConfig示例为: // {path: /about/, cb(){console.log('about')}} addRoute(routeConfig) { this.routes.push(routeConfig); } /// 👆 新增代码 } export default MiniRouter;
添加路由导航功能,其实是location
相关方法的封装
详细内容能够回看:如何导航页面?
class MiniRouter { constructor(options) { const { mode, routes, base } = options; this.mode = mode || (window.history.pushState ? 'history' : 'hash'); this.routes = routes || []; this.base = base || '/'; } addRoute(routeConfig) { this.routes.push(routeConfig); } // 添加前进、后退功能 👇 新增代码 go(n) { window.location.go(n); } back() { window.location.back(); } forward() { window.location.forward(); } /// 👆 新增代码 } export default MiniRouter;
参照vue-router,大橙子在这里设计了push、replace两种方法。其中:push
表明跳转新页面,并在历史栈中增长一条记录,用户能够后退replace
表明跳转新页面,可是不在历史栈中增长记录,用户不能够后退
若是是hash模式下
使用location.hash = newHash
来实现push跳转
使用window.location.replace(url)
来实现replace跳转
若是是history模式下
使用history.pushState()
来实现push跳转
使用history.replaceState()
来实现replace跳转
请注意:
为pushState
与replaceState
添加try...catch
是因为Safari的某个安全策略有兴趣的同窗能够查看
vue-router相关commit
Stack Overflow上的相关问题
class MiniRouter { constructor(options) { const { mode, routes, base } = options; this.mode = mode || (window.history.pushState ? 'history' : 'hash'); this.routes = routes || []; this.base = base || '/'; } addRoute(routeConfig) { this.routes.push(routeConfig); } go(n) { window.history.go(n); } back() { window.location.back(); } forward() { window.location.forward(); } // 实现导航到新路由的功能 // push表明跳转新页面,并在历史栈中增长一条记录,用户能够后退 // replace表明跳转新页面,可是不在历史栈中增长记录,用户不能够后退 //👇 新增代码 push(url) { if (this.mode === 'hash') { this.pushHash(url); } else { this.pushState(url); } } pushHash(path) { window.location.hash = path; } pushState(url, replace) { const history = window.history; try { if (replace) { history.replaceState(null, null, url); } else { history.pushState(null, null, url); } this.handleRoutingEvent(); } catch (e) { window.location[replace ? 'replace' : 'assign'](url); } } replace(path) { if (this.mode === 'hash') { this.replaceHash(path); } else { this.replaceState(path); } } replaceState(url) { this.pushState(url, true); } replaceHash(path) { window.location.replace(`${window.location.href.replace(/#(.*)$/, '')}#${path}`); } /// 👆 新增代码 } export default MiniRouter;
在history
模式下,咱们会使用location.path
来获取当前连接路径。
若是设置了base
参数,将会把base路径干掉,方便后面匹配路由地址。
在hash
模式下,咱们会使用正则匹配将#
后的地址匹配出来。
固然全部操做以后,将会把/
彻底去掉。
class MiniRouter { constructor(options) { const { mode, routes, base } = options; this.mode = mode || (window.history.pushState ? 'history' : 'hash'); this.routes = routes || []; this.base = base || '/'; } addRoute(routeConfig) { this.routes.push(routeConfig); } go(n) { window.history.go(n); } back() { window.location.back(); } forward() { window.location.forward(); } push(url) { if (this.mode === 'hash') { this.pushHash(url); } else { this.pushState(url); } } pushHash(path) { window.location.hash = path; } pushState(url, replace) { const history = window.history; try { if (replace) { history.replaceState(null, null, url); } else { history.pushState(null, null, url); } this.handleRoutingEvent(); } catch (e) { window.location[replace ? 'replace' : 'assign'](url); } } replace(path) { if (this.mode === 'hash') { this.replaceHash(path); } else { this.replaceState(path); } } replaceState(url) { this.pushState(url, true); } replaceHash(path) { window.location.replace(`${window.location.href.replace(/#(.*)$/, '')}#${path}`); } // 实现获取路径功能 //👇 新增代码 getPath() { let path = ''; if (this.mode === 'history') { path = this.clearSlashes(decodeURI(window.location.pathname)); path = this.base !== '/' ? path.replace(this.base, '') : path; } else { const match = window.location.href.match(/#(.*)$/); path = match ? match[1] : ''; } // 可能还有多余斜杠,所以须要再清除一遍 return this.clearSlashes(path); }; clearSlashes(path) { return path.toString().replace(/\/$/, '').replace(/^\//, ''); } /// 👆 新增代码 } export default MiniRouter;
在实例化路由时,咱们将会按照mode的不一样,在页面上挂载不一样的事件监听器:
hashchange
事件进行监听popstate
事件进行监听在监听到变化后,回调方法将会遍历咱们的路由表,若是符合路由的正则表达式,就执行相关路由的回调方法。
class MiniRouter { constructor(options) { const { mode, routes, base } = options; this.mode = mode || (window.history.pushState ? 'history' : 'hash'); this.routes = routes || []; this.base = base || '/'; this.setupListener(); // 👈 新增代码 } addRoute(routeConfig) { this.routes.push(routeConfig); } go(n) { window.history.go(n); } back() { window.location.back(); } forward() { window.location.forward(); } push(url) { if (this.mode === 'hash') { this.pushHash(url); } else { this.pushState(url); } } pushHash(path) { window.location.hash = path; } pushState(url, replace) { const history = window.history; try { if (replace) { history.replaceState(null, null, url); } else { history.pushState(null, null, url); } this.handleRoutingEvent(); } catch (e) { window.location[replace ? 'replace' : 'assign'](url); } } replace(path) { if (this.mode === 'hash') { this.replaceHash(path); } else { this.replaceState(path); } } replaceState(url) { this.pushState(url, true); } replaceHash(path) { window.location.replace(`${window.location.href.replace(/#(.*)$/, '')}#${path}`); } getPath() { let path = ''; if (this.mode === 'history') { path = this.clearSlashes(decodeURI(window.location.pathname)); path = this.base !== '/' ? path.replace(this.base, '') : path; } else { const match = window.location.href.match(/#(.*)$/); path = match ? match[1] : ''; } // 可能还有多余斜杠,所以须要再清除一遍 return this.clearSlashes(path); }; clearSlashes(path) { return path.toString().replace(/\/$/, '').replace(/^\//, ''); } // 实现监听路由,及处理回调功能 //👇 新增代码 setupListener() { this.handleRoutingEvent(); if (this.mode === 'hash') { window.addEventListener('hashchange', this.handleRoutingEvent.bind(this)); } else { window.addEventListener('popstate', this.handleRoutingEvent.bind(this)); } } handleRoutingEvent() { if (this.current === this.getPath()) return; this.current = this.getPath(); for (let i = 0; i < this.routes.length; i++) { const match = this.current.match(this.routes[i].path); if (match) { match.shift(); this.routes[i].cb.apply({}, match); return; } } } /// 👆 新增代码 } export default MiniRouter;
实例化以前实现的MiniRouter
,是否是和日常写的router很像(除了功能少了不少😝)?
相关代码以下:
import MiniRouter from './MiniRouter'; const router = new MiniRouter({ mode: 'history', base: '/', routes: [ { path: /about/, cb() { app.innerHTML = `<h1>这里是关于页面</h1>`; } }, { path: /news\/(.*)\/detail\/(.*)/, cb(id, specification) { app.innerHTML = `<h1>这里是新闻页</h1><h2>您正在浏览id为${id}<br>渠道为${specification}的新闻</h2>`; } }, { path: '', cb() { app.innerHTML = `<h1>欢迎来到首页!</h1>`; } } ] });
完整的代码,请跳转至:github传送门
下载代码后,执行下面的代码,进行调试:
npm i npm run dev
常见的react-router
和vue-router
传入的路径都是字符串,而上面实现的例子中,使用的是正则表达式。那么如何才能作到解析字符串呢?
看看这两个开源路由,咱们都不难发现,它们都使用了path-to-regexp这个库。假如咱们传入了一个路径:
/news/:id/detail/:channel
使用match方法
import { match } from "path-to-regexp"; const fn = match("/news/:id/detail/:channel", { decode: decodeURIComponent }); // {path: "/news/122/detail/baidu", index: 0, params: {id: "122", channel: "baidu"}} console.log(fn("/news/122/detail/baidu")); // false console.log(fn("/news/122/detail"));
是否是很眼熟?和咱们日常使用路由库时,使用相关参数的路径一致。有兴趣的同窗,能够沿着这个思路将路由优化一下
咱们发现,当知足咱们愈来愈多需求的时候,代码库也变得愈来愈庞大。可是最核心的内容,永远只有那一些,主要抓住了主线,实际上分支的理解就会简单起来。
本文的代码主要参考自开源做者navigo的文章,在此基础上,为了贴合vue-router的相关配置。作了一些改动,因为水平受限,文内若有错误,还望你们在评论区内提出,以避免误人子弟。
navigo
的做者。建议能够读一读原文,一样能够启发你的思惟。