ips hash 和 history 中都会记录浏览历史,保存在浏览器的路由栈中
window.location对象 例如:https://www.baidu.com:443/test#/params hash : 设置或返回从 (#) 开始的 URL(锚)。#/params host : 设置或返回主机名和当前 URL 的端口号 www.baidu.com:443。 hostname:设置或返回当前 URL 的主机名www.baidu.com。 href : 设置或返回完整的 URL。https://www.baidu.com:443/test#/params pathname: 设置或返回当前 URL 的路径部分。/test port:设置或返回当前 URL 的端口号。443 search : 设置或返回从问号 (?) 开始的 URL(查询部分)。 assign() : 加载新的文档。 reload() : 重新加载当前文档。 replace() : 用新的文档替换当前文档。
hash即浏览器url中 #后面的内容通过 window.location.hash 来获取内容 www.baidu.com/这里是什么内容都是忽略的ssss#内容 详细如图 通过 hashchange 事件来监听浏览器的 hash值的改变, 渲染响应路由页面 if('onhashchange' in window) { window.addEventListener('hashchange',function(e){ console.log(window.location.hash) },false) }history 对象方法
go() :接受一个整数为参数,移动到该整数指定的页面,比如history.go(1)相当于history.forward(),history.go(-1)相当于history.back(),history.go(0)相当于刷新当前页面
back() :移动到上一个访问页面,等同于浏览器的后退键,常见的返回上一页就可以用back(),是从浏览器缓存中加载,而不是重新要求服务器发送新的网页
forward() :移动到下一个访问页面,等同于浏览器的前进键
pushState():
history.pushstate(state,title,url) state: 一个与指定网址相关的状态对象,popState事件触发时,该对象会传入回调函数,如果不需要这个对象,此处可填null title: 新页面的标题,但是所有浏览器目前都忽略这个值,因此这里可以填null url: 新的网址,必须与当前页面处在同一个域,浏览器的地址栏将显示这个网址
replaceState(): history.replaceState()方法的参数和pushState()方法一摸一样,区别是它修改浏览器历史当中的记录
length:history.length属性保存着历史记录的url数量,初始时该值为1,如果当前窗口先后访问了三个网址,那么history对象就包括3项,history.length=3 state:返回当前页面的state对象。可以通过replaceState()和pushState()改变state,可以存储很多数据
scrollRestoration history.scrollRestoration = 'manual'; 关闭浏览器自动滚动行为 history.scrollRestoration = 'auto'; 打开浏览器自动滚动行为(默认)
window.location.href.replace(window.location.origin, '') 获取记录内容
通过popstate事件来监听history模式, 渲染响应路由页面popState 事件
每当同一个文档的浏览历史(即history)出现变化时,就会触发popState事件
注意:仅仅调用pushState方法或replaceState方法,并不会触发该事件,只有用户点击浏览器后退和前进按钮时,或者使用js调用back、forward、go方法时才会触发。另外该事件只针对同一个文档,如果浏览历史的切换,导致加载不同的文档,该事件不会被触发
使用的时候,可以为popState事件指定回调函数
window.onpopstate = function (event) { console.log('location: ' + document.location); console.log('state: ' + JSON.stringify(event.state)); }; // 或者 window.addEventListener('popstate', function(event) { console.log('location: ' + document.location); console.log('state: ' + JSON.stringify(event.state)); });回调函数的参数是一个event事件对象,它的 state 属性指向 pushState 和 replaceState 方法为当前url所提供的状态对象(即这两个方法的第一个参数)。上边代码中的event.state就是通过pushState和replaceState方法为当前url绑定的state对象
这个state也可以直接通过history对象读取 history.state
注意:页面第一次加载的时候,浏览器不会触发popState事件
window.addEventListener('popstate', e => { console.log(window.location.href.replace(window.location.origin, '')); })完整的DEMO
<!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>原生实现hash和browser两种路由模式</title> </head> <body> <div class="router_box"> <a href="/home" class="router" replace="true">主页</a> <a href="/news" class="router">新闻</a> <a href="/team" class="router">团队</a> <a href="/about" class="router">关于</a> <a href="/abcd" class="router">随便什么</a> </div> <div id="router-view"></div> <script> function Router(params){ // 记录routes配置 this.routes = params.routes || []; // 记录路由模式 this.mode = params.mode || 'hash'; console.log('this.mode', this.mode); // 初始化 this.init = function(){ // 绑定路由响应事件 var that = this; document.querySelectorAll(".router").forEach((item,index)=>{ item.addEventListener("click",function(e){ // 阻止a标签的默认行为 if ( e && e.preventDefault ){ e.preventDefault(); }else{ window.event.returnValue = false; } if (that.mode == 'hash'){ // 判断是replace方法还是push方法 if (this.getAttribute("replace")){ var i = window.location.href.indexOf('#') // 通过replace方法直接替换url window.location.replace( window.location.href.slice(0, i >= 0 ? i : 0) + '#' + this.getAttribute("href") ) }else{ // 通过赋值追加 window.location.hash = this.getAttribute("href"); } }else{ if (this.getAttribute("replace")){ window.history.replaceState({}, '', window.location.origin+this.getAttribute("href")) that.routerChange(); }else{ window.history.pushState({}, '', window.location.origin+this.getAttribute("href")) that.routerChange(); } } }, false); }); // 监听路由改变 if (this.mode == 'hash'){//hash模式时监听hashchange window.addEventListener("hashchange",()=>{ this.routerChange(); }); }else{//history模式时监听popstate事件 window.addEventListener('popstate', e => { console.log(123); this.routerChange(); }) } this.routerChange(); }, // 路由改变监听事件 this.routerChange = function(){ if (this.mode == 'hash'){ let nowHash=window.location.hash; let index=this.routes.findIndex((item,index)=>{ return nowHash == ('#'+item.path); }); if(index>=0){ document.querySelector("#router-view").innerHTML=this.routes[index].component; }else { let defaultIndex=this.routes.findIndex((item,index)=>{ return item.path=='*'; }); if(defaultIndex>=0){ const i = window.location.href.indexOf('#') window.location.replace( window.location.href.slice(0, i >= 0 ? i : 0) + '#' + this.routes[defaultIndex].redirect ) } } }else{ let path = window.location.href.replace(window.location.origin, ''); let index=this.routes.findIndex((item,index)=>{ console.log('path...', path, 'item.path...', item.path); return path == item.path; }); if(index>=0){ document.querySelector("#router-view").innerHTML=this.routes[index].component; }else { let defaultIndex=this.routes.findIndex((item,index)=>{ return item.path=='*'; }); if(defaultIndex>=0){ console.log(window.location.origin+this.routes[defaultIndex].redirect) window.history.pushState({}, '', window.location.origin+this.routes[defaultIndex].redirect) this.routerChange(); } } } } // 调用初始化 this.init(); } new Router({ mode: 'hash', routes:[ { path: '/home', component: '<h1>主页</h1><h4>新一代前端工程师:我们啥都会</h4>' }, { path: '/news', component: '<h1>新闻</h1><h4>今天2020-07-01</h4>' }, { path: '/team', component: '<h1>团队</h1><h4>WEB前端工程师</h4>' }, { path: '/about', component: '<h1>关于</h1><h4>我们都要加油</h4>' }, { path:'*', redirect:'/home'} ] }); </script> </body> </html>加深理解阅读 route-link 和 route-view
官方API文档
该组件支持用户在具有路由功能的应用中(点击)导航,默认渲染成带有正确链接的<a>标签,可以通过tag属性生成别的标签。
它本质上是通过在生成的标签上绑定了click事件,然后执行对应的VueRouter实例的push()实现的,对于router-link组件来说,可以传入以下props:
to 表示目标路由的链接,当被点击后,内部会立刻把to的值传到router.push(),所以这个值可以是一个字符串或者是描述目标位置的对象tag router-link组件渲染的标签名,默认为aexact 布尔类型,“是否激活”默认类名的依据是包含匹配append 布尔类型,设置append属性后,则在当前(相对)路劲前添加基路径replace 布尔类型,设置replace后,当点击时会调用router.replace()而不是router.push(),这样导航后不会留下history记录activeClass 链接激活时使用的CSS类名exactActiveClass 配置当链接被精确匹配的时候应该激活的 classevent 声明可以用来触发导航的事件。可以是一个字符串或是一个包含字符串的数组 var Link = { name: 'RouterLink', props: { to: { type: toTypes, required: true }, tag: { type: String, default: 'a' }, exact: Boolean, append: Boolean, replace: Boolean, activeClass: String, exactActiveClass: String, event: { type: eventTypes, default: 'click' } }, render: function render (h) { var this$1 = this; var router = this.$router; var current = this.$route; var ref = router.resolve(this.to, current, this.append); var location = ref.location; var route = ref.route; var href = ref.href; var classes = {}; var globalActiveClass = router.options.linkActiveClass; var globalExactActiveClass = router.options.linkExactActiveClass; // 支持全局空 Active Class var activeClassFallback = globalActiveClass == null ? 'router-link-active' : globalActiveClass; var exactActiveClassFallback = globalExactActiveClass == null ? 'router-link-exact-active' : globalExactActiveClass; var activeClass = this.activeClass == null ? activeClassFallback : this.activeClass; var exactActiveClass = this.exactActiveClass == null ? exactActiveClassFallback : this.exactActiveClass; var compareTarget = location.path ? createRoute(null, location, null, router) : route; classes[exactActiveClass] = isSameRoute(current, compareTarget); classes[activeClass] = this.exact ? classes[exactActiveClass] : isIncludedRoute(current, compareTarget); var handler = function (e) { if (guardEvent(e)) { if (this$1.replace) { router.replace(location); } else { router.push(location); } } }; var on = { click: guardEvent }; if (Array.isArray(this.event)) { this.event.forEach(function (e) { on[e] = handler; }); } else { on[this.event] = handler; } var data = { class: classes }; if (this.tag === 'a') { data.on = on; data.attrs = { href: href }; } else { // 找到第一个< a >子级,并应用侦听器和href var a = findAnchor(this.$slots.default); if (a) { // 如果< a >是静态节点 a.isStatic = false; var aData = a.data = extend({}, a.data); aData.on = on; var aAttrs = a.data.attrs = extend({}, a.data.attrs); aAttrs.href = href; } else { // 没有< a >子代,将侦听器应用于自身 data.on = on; } } return h(this.tag, data, this.$slots.default) } }router-view是一个 functional 组件,渲染路径匹配到的视图组件。<router-view> 渲染的组件还可以内嵌自己的 <router-view>,根据嵌套路径,渲染嵌套组件
它只有一个名为name的props,这个name还有个默认值,就是default,一般情况下,我们不用传递name,只有在命名视图的情况下,我们需要传递name,命名视图就是在同级展示多个视图,而不是嵌套的展示出来,
router-view组件渲染时是从VueRouter实例._route.matched属性获取需要渲染的组件,也就是我们在vue内部的this.$route.matched上获取的
var View = { name: 'RouterView', functional: true, props: { name: { type: String, default: 'default' } }, render: function render (_, ref) { var props = ref.props; var children = ref.children; var parent = ref.parent; var data = ref.data; // 由devtools用来显示路由器视图标记 data.routerView = true; // 直接使用父上下文的createElement()函数 // 以便由router-view呈现的组件能够解析命名的插槽 var h = parent.$createElement; var name = props.name; var route = parent.$route; var cache = parent._routerViewCache || (parent._routerViewCache = {}); // 确定当前视图深度,同时检查树是否 // 已被切换为非活动但保持活动状态 var depth = 0; var inactive = false; while (parent && parent._routerRoot !== parent) { if (parent.$vnode && parent.$vnode.data.routerView) { depth++; } if (parent._inactive) { inactive = true; } parent = parent.$parent; } data.routerViewDepth = depth; // 如果树处于非活动状态并且保持活动状态,则渲染上一视图 if (inactive) { return h(cache[name], data, children) } var matched = route.matched[depth]; // 如果没有匹配的路由,则呈现空节点 if (!matched) { cache[name] = null; return h() } var component = cache[name] = matched.components[name]; // 附加实例注册挂钩 // 这将在实例的注入生命周期钩子中调用 data.registerRouteInstance = function (vm, val) { // 对于注销,val可能未定义 var current = matched.instances[name]; if ( (val && current !== vm) || (!val && current === vm) ) { matched.instances[name] = val; } } // 同时在预缓存挂钩中注册实例 // 如果同一组件实例在不同的路由上被重用 ;(data.hook || (data.hook = {})).prepatch = function (_, vnode) { matched.instances[name] = vnode.componentInstance; }; // 解析 props var propsToPass = data.props = resolveProps(route, matched.props && matched.props[name]); if (propsToPass) { // clone to prevent mutation propsToPass = data.props = extend({}, propsToPass); // 将未声明的props具作为属性传递 var attrs = data.attrs = data.attrs || {}; for (var key in propsToPass) { if (!component.props || !(key in component.props)) { attrs[key] = propsToPass[key]; delete propsToPass[key]; } } } return h(component, data, children) } }