hash原理:hash router 有一个明显的标志是url 中带有#, 我们可以通过onhashchange监听url中的hash来进行路由跳转
# 后面的 fragment 发生改变时,页面不会重新请求,其他参数发生变化,都会引起页面的重新请求
Onhashchange事件触发条件:
这是一个HTML 5新增的事件,当#值发生变化时,就会触发这个事件。IE8+、Firefox 3.6+、Chrome 5+、Safari 4.0+支持该事件。
它的使用方法有三种:
window.onhashchange = func;
window.addEventListener("hashchange", func, false);
对于不支持onhashchange的浏览器,可以用setInterval监控location.hash的变化。
参考:
url中#(hash)的含义
首页
关于
history原理:利用pushState改变路径,利用popstate事件,实现history路由拦截,监听页面返回事件
$router.go(-1) === history.go(-1) // 浏览器的后退操作
2)History.pushState(stateObj,title,url)
pushState不会刷新页面,只是网站(history对象)发生变化,只有触发前进后退事件back()和forward()时浏览器才会刷新
这里的url是受同源策略限制,防止恶意脚本模仿其他网站url用来欺骗用户,所以当违背同源策略时将会报错
3)popstate事件:实现history路由拦截,监听页面返回事件
同一个文档的浏览历史(history对象)出现变化时,就会触发popstate事件
注意:仅仅调用pushState方法或replaceState方法,并不会触发该事件,只有用户点击浏览器倒退前进或back、forward、go才会触发。
首页
关于
启动本地服务器看
hash模式 和 history模式 abstract模式
1)abstract模式:
适用于所有JavaScript环境,例如服务器端和Node.js. 如果没有浏览器API,路由器将自动强制进入此模式。
2)hash模式:hash router 有一个明显的标志是url 中带有#, 我们可以通过监听url中的hash来进行路由跳转
①就是指 url 尾巴后的 # 号以及后面的字符, 请求的时候不会被包含在 http 请求中 只会携带#之前的,所以每次改变hash不会重新请求加载页面
②hash 改变会触发 hashchange 事件
③hash变化会被浏览器记录,浏览器的前进和后退都能用。
④能兼容到ie8
3)history模式:history 为 HTML5 Api,提供了丰富的router 相关属性, 比如history.back() 就能轻松的做到页面
①页面请求时会带上整个链接,所以后台需要做相对处理,不然返回404
②window.history.pushState(state, title, url) // 改变网址
需要注意的是调用 history.pushState() 或 history.replaceState() 用来在浏览历史中添加或修改记录,不会触发popstate事件
③window.history.back(); // 后退
window.history.forward(); // 前进
window.history.go(-3); // 后退三个页面
④history 只能兼容到 IE10
在hash模式下,前端路由修改的是#中的信息,而浏览器请求时是不带它玩的,所以没有问题.但是在history下,你可以自由的修改path,当刷新时,如果服务器中没有相应的响应或者资源,会分分钟刷出一个404来。
hash模式url中会带有#号,破坏url整体的美观性, history 需要服务端支持rewrite, 否则刷新会出现404现象
hashHistory:简单 浏览器支持好 不会和服务端路由耦合
缺点:http请求没有前端路由信息、锚点功能失效、对seo不友好
browserHistory:单点登录时能带上前端路由,能统计到前端路由访问情况,对seo友好
缺点:分不清前端与后端、需要服务端支持
/**
* 1、配置hash、history路由,传进来的参数有哪些
* 2、匹配到对应的路由表
* 3、渲染视图
*/
class HistoryRoute {
constructor () {
this.current = null
console.log(this.current)
}
}
class VueRouter {
constructor (options) {
this.mode = options.mode || 'hash'
this.routes = options.routes || []
// 把数组变成下面的对象
// 你传递的这个路由表是一个数组 {'/home':Home , '/about':About}
this.routesMap = this.createMap(this.routes)
console.log(this.routesMap)
// 路由中需要存放当前的路径 需要状态
// this.history = { current: null } // 每次切换,把里面的值变成 {current:'/home'} {current:'/about'}
this.history = new HistoryRoute()
// 把内容渲染到页面
this.init() // 开始初始化操作
}
init () {
if (this.mode === 'hash') {
// 先判断用户打开时有没有hash 没有就跳转到 #/
location.hash ? '' : location.hash = '/'
window.addEventListener('load', () => {
// 在页面进来时初始化html
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
})
}
}
go () {
}
back () {
}
push () {
}
createMap (routes) {
return routes.reduce((memo, current) => {
memo[current.path] = current.component
return memo
}, {})
}
}
VueRouter.install = function (Vue, opts) {
// 每个组件的实例都有this.$router / this.roue这两个属性
// 所有组件中怎么拿到同一个路由的实例
Vue.mixin({
beforeCreate () {
// 获取组件的属性名字
// 给当前实例定义$router属性
if (this.$options && this.$options.router) { // 定位根组件
// this._root = this // 把当前实例挂载在_root上 this==> vue
Object.defineProperty(this, '_root', { // Router的实例
get () {
return this
}
})
this._router = this.$options.router // 把router实例挂载在_router上
// observer方法 深度劫持
/**
* 如果history中的current属性变化 也会刷新视图
* this.xxx = this._router.history
*/
Vue.util.defineReactive(this, 'xxx', this._router.history)
} else {
// vue组件的渲染顺序 父->子->孙子
// this._root = this.$parent._root // 如果想获取唯一的路由实例this._root._router
Object.defineProperty(this, '_root', { // Router的实例
get () {
return this.$parent._root
}
})
}
Object.defineProperty(this, '$router', { // Router的实例
get () {
return this._root._router
}
})
Object.defineProperty(this, '$route', {
get () {
return {
// 当前的路由所在的状态
current: this._root._router.history.current
}
}
})
}
})
Vue.component('router-link', {
props: {
to: String,
tag: String
},
methods: {
handleClick () {
// mode === 'hash' ? `#${this.to}` : this.to
// 如果是hash怎么跳转 如果是history怎么跳转
let mode = this._self._root._router.mode
// let current = this._self.$router.history.current
if (mode === 'hash') {
// this.href = `#${this.to}`
this._self.$router.history.current = `#${this.to}`
} else {
this._self.$router.history.current = this.to
}
}
},
// jxs用法
render (h) {
// return h('a', {}, '首页') // react createElement
let mode = this._self._root._router.mode
let tag = this.tag || 'a'
// 深度渲染
return {this.$slots.default}
}
})
// 根据current(当前路径)找出对应的组件,通过渲染函数render渲染出来
Vue.component('router-view', { // 根据当前的状态 current路由表{'/about':About}
render (h) {
console.log(this)
// 如何将current变成动态的 current变化应该会影响视图刷新
// vue事项双向绑定 Object.defineProperty set get
let current = this._self.$router.history.current
let routesMap = this._self.$router.routesMap
console.log(routesMap)
return h(routesMap[current])
}
})
}
export default VueRouter
代码参考
整个webapp就一个HTML文件,里面的各个功能页面是JavaScript通过hash或者history api来进行路由,并通过ajax 拉取数据实现响应功能。
优点:
分离前后端关注点,前端负责界面显示,后端负责数据存储和计算,各司其职,不会把前后端的逻辑混杂在一起;
减轻服务器压力,服务器只用出数据就可以,不用管展示逻辑和页面合成,吞吐能力会提高几倍;
同一套后端程序代码,不用修改就可以用于Web界面、手机、平板等多种客户端;
缺点:
SEO问题,现在可以通过Prerender等技术解决一部分;
前进、后退、地址栏等,需要程序进行管理;
书签,需要程序来提供支持;
document.getElementById("main").innerHTML = "404";
window.onhashchange = function() {
let page = location.hash;
if (page === "#home") {
document.getElementById("main").innerHTML = "这是首页";
return;
} else if (page === "#help") {
document.getElementById("main").innerHTML = "这是帮助页面";
return;
}
};
1)全局守卫
router.beforeEach 路由前置时触发
const router = new VueRouter({ ... })
// to 要进入的目标路由对象
// from 当前的路由对象
// next resolve 这个钩子,next执行效果由next方法的参数决定
// next() 进入管道中的下一个钩子
// next(false) 中断当前导航
// next({path}) 当前导航会中断,跳转到指定path
// next(error) 中断导航且错误传递给router.onErr回调
// 确保前置守卫要调用next,否然钩子不会进入下一个管道
router.beforeEach((to, from, next) => {
// ...
})
router.afterEach 路由后置时触发
// 与前置守卫基本相同,不同是没有next参数
router.afterEach((to, from) => {
// ...
})
router.beforeResolve 跟router.beforeEach类似,在所有组件内守卫及异步路由组件解析后触发
2)路由独享守卫
参数及意义同全局守卫,只是定义的位置不同
const router = new VueRouter({
routes: [
{
path: '/',
component: Demo,
beforeEnter: (to, from, next) => {
// ...
},
afterEnter: (to, from, next) => {
// ...
},
}
]
})
3)组件独享守卫
组件内新一个守卫, beforeRouteUpdate,在组件可以被复用的情况下触发,如 /demo/:id, 在/demo/1 跳转/demo/2的时候,/demo 可以被复用,这时会触发beforeRouteUpdate
const Demo = {
template: `...`,
beforeRouteEnter (to, from, next) {
...
},
// 在当前路由改变,但是该组件被复用时调用
beforeRouteUpdate (to, from, next) {
...
},
beforeRouteLeave (to, from, next) {
...
}
}
注意:注意在beforeRouteEnter前不能拿到当前组件的this,因为组件还有被创建,我们可以通过next(vm => {console.log(vm)}) 回传当前组件的this进行一些逻辑操作
1)触发进入其他路由。
2)调用要离开路由的组件守卫beforeRouteLeave
3)调用局前置守卫:beforeEach
4)在重用的组件里调用 beforeRouteUpdate
5)调用路由独享守卫 beforeEnter。
6)解析异步路由组件。
7)在将要进入的路由组件中调用beforeRouteEnter
8)调用全局解析守卫 beforeResolve
9)导航被确认。
10)调用全局后置钩子的 afterEach 钩子。
11)触发DOM更新(mounted)。
12)执行beforeRouteEnter 守卫中传给 next 的回调函数
参考
1)router-link
// path:'/home' 不配置path ,第一次可请求,刷新页面id会消失
// query传参数 (类似get,url后面会显示参数)
2)this.$router.push() 函数里面调用
this.$router.push({name:'home',query: {id:'1'}})
this.$router.push({path:'/home',query: {id:'1'}})
this.$router.push({name:'home',params: {id:'1'}}) // 只能用 name
3)this.$router.replace() 用法同上
4)this.$router.go(n)
扩展:
1、区别?
this. r o u t e r . p u s h 跳 转 到 指 定 u r l 路 径 , 并 想 h i s t o r y 栈 中 添 加 一 个 记 录 , 点 击 后 退 会 返 回 到 上 一 个 页 面 t h i s . router.push 跳转到指定url路径,并想history栈中添加一个记录,点击后退会返回到上一个页面 this. router.push跳转到指定url路径,并想history栈中添加一个记录,点击后退会返回到上一个页面this.router.replace
跳转到指定url路径,但是history栈中不会有记录,点击返回会跳转到上上个页面 (就是直接替换了当前页面)
this.$router.go(n)
向前或者向后跳转n个页面,n可为正整数或负整数
2、query和params区别
query类似 get, 跳转之后页面 url后面会拼接参数,类似?id=1, 非重要性的可以这样传, 密码之类还是用params刷新页面id还在
params类似 post, 跳转之后页面 url后面不会拼接参数 , 但是刷新页面id 会消失