大纲
hash路由
history路由
vue-router简单实现
vue中的数组,对象添加属性的监听
(1)Hash
- url 的 hash 以 # 开头,原本是用来作为锚点,从而定位到页面的特点区域
- 当 hash 改变时,页面不会刷新,浏览器也不会向服务器发送请求
- 注意:hash 改变时,会触发 hashchange 事件,hashchange回调函数中,可以请求数据,实现页面的更新操作
Hash
1. 作为锚点
- 点击a2会跳转到div2上,同时url中的hash部分会变化
<a href="#anchor1">锚点1</a>
<a href="#anchor2">锚点2</a>
<div id="anchor1" class="anchor1">锚点1</div>
<div id="anchor2" class="anchor2">锚点2</div>
2. 当页面的url的hash改变时,会触发 hashchange 事件,可以事件更新的更新操作
<body>
<a href="#anchor1">锚点1</a>
<a href="#anchor2">锚点2</a>
<script>
window.addEventListener('hashchange', function() {
console.log('111111111')
}, false)
</script>
</body>
3. 手动实现 hash-router
原理:
(1)url的hash改变时,触发hashchange事件
(2)hashchange事件触发时,更新视图
- html部分
<a href="#/home">home</a>
<a href="#/other">other</a>
<div id="content">内容部分</div>
- js部分
const routes = [{ // 路由配置数组
path: '/home',
component: '<h1>home页面<h1/>'
}, {
path: '/other',
component: '<h1>other页面</h1>'
}]
class Router {
constructor(routes) {
this.route = {}
routes.forEach(item => {
// 循环配置项,path作为key, value是一个更新html的函数
this.route[item.path] = () => { document.getElementById('content').innerHTML = item.component }
})
this.init()
}
init () {
window.addEventListener('load', this.updateView, false) // 内容加载完成触发
window.addEventListener('hashchange', this.updateView, false) // hash改变触发,更新视图
}
updateView = () => {
const hash = window.location.hash.slice(1) || '/' // 截取hash
this.route[hash] && this.route[hash]() // 如果该路由存在,就加载路由对应的组件
}
}
const router = new Router(routes)
(2) History
History对象的属性
(1)History.length:当前窗口访问过的网址数量,包括当前页面
(2)History.state:History堆栈最上层的状态值History对象的方法:
back()
forward()
go()
History.pushState(state, title, url) ------- 用于在历史中添加一条记录
(1)state:一个与添加的记录相关联的 对象,主要用于popstate
事件。在popstate事件触发时,浏览器会将这个对象序列化后保存在本地,从新载入这个页面时,能拿到这个对象
(2)title:新页面的标题,现在所有浏览器都忽略这个参数,可以填空字符串
(3)url:新的网址,必须与当前页面在同一个域内,浏览器的地址栏将显示这个网址
(4)pushState()方法不会触发页面刷新,只是导致 History 对象发生变化,地址栏会有反应。
History.replaceState() ------------------- 用来修改 History对象 的当前记录
-
popstate 事件 ------- 每当同一个文档的浏览历史出现变化时,就会触发popstate事件
(1)注意:popstate事件触发的时机:如果仅仅是改变 History.pushState 和 History.replaceState 不会触发 popstate,但是History.pushState 和 History.replaceState 可以改变url,(不会触发 popstate 但会 修改地址栏的url)
(2)popstate触发的条件:- 浏览器的进退按钮,
- 或者 History.go(),History.back(),History.forward()等
html5中通过
History.pushState()
和History.replaceState()
来进行路由控制,通过这两个方法可以实现改变url,且实现不向服务器发送请求,不存在#号,比hash路由更美观但是 History 路由需要服务器的支持,并且需将所有的路由重定向到根页面
History
前置知识点:
1. history
- html5中通过 history.pushState 和 hitstory.repalceState 来控制路由
- history.pushState 和 history.replaceState 只会改变 History对象,会改变url,但不会更新页面
- 所以当history对象变化时,需要手动触发一个事件
- 1. 封装一个方法,在 pushState()或者replaceState()之后调用一个方法
- 2. 通过浏览器history.back() go() forward()等方法触发 popState 事件
2. location对象
- host 主机名和端口号
- hostname 主机名
- pathname 返回url的路径部分,---------------- /之后 ?之前
- href 返回完整的url ------------------------- <a href=""> a标签中也有href属性
- search 以 ? 开始至行尾 或 # --------------- ?开始到#号之前,如果没有#就到url结束(包括?号)
- hash 以 # 开始至行尾 ----------------------- hash部分不会上传到服务器(包括#号)
- port 端口号
- protocol 协议 ------------------------------ 包括结尾的: (http:)
- origin 协议域名端口
<body>
<div>
<a href="javascript:void(0)" data-href="/home">home</a>
<a href="javascript:void(0)" data-href="/other">other</a>
<div id="content">content</div>
</div>
<script>
const routes = [{ // 路由配置数组
path: '/home',
component: '<h1>home页面1<h1/>'
}, {
path: '/other',
component: '<h1>other页面2</h1>'
}]
class Router {
constructor (toutes) {
this.route = {}
toutes.forEach(item => {
this.route[item.path] = () => {
document.getElementById('content').innerHTML = item.component
}
})
this.bindEvent()
this.init()
}
init () {
window.addEventListener('load', this.updateView, false)
window.addEventListener('popstate', this.updateView, false)
// 监听popstate事件
// popstate事件触发的条件
// 1. history.go() history.foward() history.back() 等history上的事件
// 2. 浏览器的前进,后退按钮
// 注意:本代码中监听popstate,只要是为了浏览器的前进后退时,触发
}
updateView = () => {
const path = window.location.pathname || '/' // 获取pathname
this.route[path] && this.route[path]()
}
bindEvent = () => {
const that = this
const a = document.getElementsByTagName('a')
Array.prototype.forEach.call(a, item => {
// 循环a标签,添加点击事件,拿到 data-href属性
item.addEventListener('click', function () {
that.push(item.getAttribute('data-href'))
}, false)
})
}
push = (url) => {
window.history.pushState({}, null, url)
// 修改 History 对象,可以改变地址栏url
// 这里 pushState 主要是为了改变 url,当url改变后,调用updateView() 拿到更新后的url,再更新视图
this.updateView()
}
}
new Router(routes)
</script>
<body>
实现一个简单的 vue-router
<!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>
<!-- 引入vue-cdn -->
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body >
<div id="app">
<p>
<!-- 注意 router-link组件具有 to 属性 -->
<router-link to="#/home">home</router-link>
<router-link to="#/other">route</router-link>
</p>
<router-view></router-view>
</div>
<script>
// 创建两个组件
const Home = { template: '<h1>home组件</h1>' }
const Other = { template: '<h1>ohter组件</h1>' }
const routes = [{
path: '/home',
component: Home
}, {
path: '/other',
component: Other
}]
class VueRouter {
constructor(Vue, routes) {
this.$options = routes
this.routeMap = {} // 路由映射对象
this.createRouteMap(this.$options) // 创建路由映射关系
// 创建app属性,是vue实列对象,挂载 data
// this.app.current 可以访问到 vue data中的 currentHash
// var data = { a: 1 }
// var vm = new Vue({
// data: data
// })
// vm.a = data.a
this.app = new Vue({
data: {
currentHash: '#/'
}
})
this.init() // 初始化监听函数
this.initComponent(Vue)
}
// createRouteMap 创建路由映射
// 当地址栏的url的 hash 变化时,找到对应的组件
// 注意 otpions是一个对象
createRouteMap = (options) => {
options.routes.forEach(item => {
this.routeMap[item.path] = item.component
})
}
init = () => {
window.addEventListener('load', this.onHashChange, false)
window.addEventListener('hashchange', this.onHashChange, false) // window绑定hashchange事件监听函数
}
// 当url中的hash改变时,触发hashchange事件
// 更新state中的数据 currentHash
// 更新后,会触发vue中的render方法,更新视图
onHashChange = () => {
this.app.currentHash = window.location.hash.slice(1) || '/'
}
initComponent = (Vue) => {
// router-link组件
// props to属性
// template 本质上会被处理成a标签,href属性是传入的 to 属性,内容是 slot 插入的内容
Vue.component('router-link', {
props: {
to: {
type: String,
value: ''
}
},
template: '<a :href="to"><slot></slot></a>'
})
const that = this
Vue.component('router-view', {
render(h) {
const component = that.routeMap[that.app.currentHash] // 拿到最新hash对应的组件
return h(component)
// h(component) 相当于 createElement(component)
// render: function(createElement) { return createElement(App); }
}
})
}
}
// 调用 VueRouter
// 注意 VueRouter 第二个参数是一个对象
new VueRouter(Vue, {
routes
})
new Vue({
el: '#app' // 将vue的控制范围限制到 id=app 的范围内
})
</script>
</body>
</html>
https://juejin.im/post/5cf9f75ef265da1bbf690ec7
https://juejin.im/post/5cb2c1656fb9a0688360fb2c
基础 https://juejin.im/post/5c7160d46fb9a049d236ae79
hash history 路由 模拟实现 https://juejin.im/post/5b330142e51d4558b10a9cc5
vue-router模拟实现 https://juejin.im/post/5b35dcb5f265da59a117344d
url组成 https://blog.csdn.net/hongtaochi0464/article/details/90649340
数组和对象的监听
https://cn.vuejs.org/v2/guide/list.html#%E6%B3%A8%E6%84%8F%E4%BA%8B%E9%A1%B9