我被一个关于VueRouter的问题难倒了,关于VueRouter的面试题不敢说精通,但是熟悉总可以吧,基本要点我虽然菜狗但是我也能描述一个1,2,3...但是直到我遇到了它:
请用
History模式
的实现原理去模拟hash
模式,把URL
中的#
后面的内容作为路由地址,可以通过hashchange
事件监听路由地址的变化;
本文将会为你具象化
的介绍这两种模式,让你不再是死记硬背这两种模式的区别,让别人提到"汉堡"
,你就会想到"",提到"薯条"
,就可以联想到"";
Vue Router
基础不回顾
Hash模式
和History模式
介绍
Hash模式
和History模式
原理模拟
用history模式
的原理去模拟hash模式
(原生及vue版)
完成题目:用history模式
的原理去模拟hash模式
,可以使用hashchange
事件监听路由地址的变化
部分源码简单比较
对,你没看错标题,就是不回顾,为了简洁
,太长你们不想看,我懂
08dadf0873c2a5ce06eaa5782bb56c51.gif
image.png
要是你在现在第一时间想到的区别跟这个初印象一样,那您对这两种模式的理解并不深入,面试可能这个部分会失分,以下我们将更为深入的去了解
:
#
后面的内容作为路径地址hashchange
事件history.pushState()
方法改变地址栏popstate
事件通过history.pushState,通过history.replaceState
:不会触发popstate相信大家都很熟悉这个差别,但是你有没有想过?通过history.pushState
改变地址栏
与监听popstate
事件并不是history的专利
,hash模式
也能使用
为了搞清楚这个问题,真正去理解两者的区别,我们先要搞明白以下这些问题:
hash模式
:
锚点("#")
,它有什么特性,怎么理解这个东西hashchange
有什么作用?history模式
:
history.pushState
用什么作用?这里会放在跟锚点一起说(它们有相似的特性)popstate
有什么作用?www.example.com:80/path/to/myf…[2]
image.png
#SomewhereInTheDocument
是资源本身的另一部分的锚点. 锚点表示资源中的一种“书签”,给浏览器显示位于该“加书签”位置的内容的方向。例如,在HTML文档上,浏览器将滚动到定义锚点的位置;在视频或音频文档上,浏览器将尝试转到锚代表的时间。值得注意的是,#后面的部分(也称为片段标识符)从来没有发送到请求的服务器
。
你可能想到一个URL类似普通信件的地址:协议代表你要使用的邮政服务,域名是城市或者城镇,端口则像邮政编码;路径代表着你的信件所有递送的大楼;参数则提供额外的信息,如大楼所在单元;
最后,锚点表示信件的收件人
。
在HTML文档上浏览器将滚动到定义锚点的位置:
image.png
总结:
(这很重要)
从某种程度来说, 调用
pushState()
和window.location = "#foo"
基本上一样, 他们都会在当前的document中创建和激活一个新的历史记录。但是pushState()
有以下优势:
- 新的URL可以是任何和当前URL同源的URL。但是设置
window.location
[4] 只会在你只设置锚的时候才会使当前的URL。- 非强制修改URL。相反,设置
window.location = "#foo";
仅仅会在锚的值不是#foo情况下创建一条新的历史记录。- 可以在新的历史记录中关联任何数据。
window.location = "#foo"
形式的操作,你只可以将所需数据写入锚的字符串中。
注意: pushState()
不会造成 hashchange (en-US)
事件调用, 即使新的URL和之前的URL只是锚的数据不同。
总结:
(这很重要)
手动输入url
或者刷新页面
使用的url是不带'#'(锚点)
的还是会请求服务器,当指定路径寻找不到文件/目录就会404
http://168.238.7.88:8000/jerry/id
如果后端缺少对/jerry/id
的路由处理,将返回 404 错误。
HashChangeEvent
接口表示一个变化事件,当 URL 中的片段标识符发生改变时,会触发此事件。片段标识符指 URL 中#
号和它以后的部分。
总结:
只要#和它以后的部分改变
就会触发这个事件改变
当活动历史记录条目更改时,将触发
popstate
事件。如果被激活的历史记录条目是通过对history.pushState()
的调用创建的,或者受到对history.replaceState()
的调用的影响,popstate
事件的state属性包含历史条目的状态对象的副本。
需要注意的是调用
history.pushState()
或history.replaceState()
不会触发popstate
事件。只有在做出浏览器动作时,才会触发该事件,如用户点击浏览器的回退按钮(或者在Javascript代码中调用history.back()
或者history.forward()
方法)
总结:
history.go
,history.back
,history.forword
或者做出浏览器动作
才会触发history.pushState
,history.replaceState
:不会触发popstateimage.png
别关网页,看我
Hash模式
和History模式
原理模拟// 安装
// yarn 全局安装
yarn global add light-server
// npm 全局安装
npm -g install light-server
复制代码
新建一个hash.html文件
<!DOCTYPE html>
<html lang="en">
<body>
<a href="#/">home</a>
<a href="#/about">about</a>
<a href="#/404">404</a>
<!-- 渲染路由对应的组件 -->
<div id="routerView"></div>
</body>
<script>
let routerView = document.querySelector('#routerView')
let router = {
'#/': 'homeComponent',
'#/about': 'aboutComponent',
'#/404': '404Component'
}
// 渲染对应的路由组件
function render() {
let hash = location.hash;
routerView.innerHTML = router[hash]
}
// 页面锚点发送变化
window.addEventListener('hashchange', () => {
render()
})
// 页面加载
window.addEventListener('DOMContentLoaded', () => {
// 当不存在'#'时重定向到首页#/
location.hash || (location.hash = "/")
// 渲染组件
render()
})
</script>
</html>
复制代码
在目录下运行:
light-server -s . --port 3000
复制代码
然后打开要你命3000,http://localhost:3000/hash.html[7]查看效果
简单说一下这个hash原理模拟
做了些什么:
render函数
负责渲染锚点对应路由组件
hashchange
负责监听锚点的变化
,这里的变化
是重点,记住不论是什么情况的变化,只要有锚点页面都不会刷新,那么有几种方式可以改变url的呢?
通过a标签
(如vue中的router-link),上述案例你只看到通过a标签更改,但是下面的几种方式也是可以触发hashchange
的
router类封装
的方法(如router.push)
手动
输入改变地址栏url
浏览器前进后退或者利用history.back,history.go
DOMContentLoaded
当页面没有锚点('#')时,把页面重定向到首页,并且渲染首页对应的路由组件
新建一个history.html
<!DOCTYPE html>
<html lang="en">
<body>
<a href="/">home</a>
<a href="/about">about</a>
<a href="/404">404</a>
<!-- 渲染路由对应的组件 -->
<div id="routerView"></div>
</body>
<script>
let routerView = document.querySelector('#routerView')
let router = {
'/': 'homeComponent',
'/about': 'aboutComponent',
'/404': '404Component'
}
// 绑定事件
function listeners() {
let aDoms = document.getElementsByTagName('a')
Array.from(aDoms).forEach(el => el.addEventListener('click', function (e) {
e.preventDefault()
history.pushState(null, '', el.getAttribute('href'))
render()
})
)
}
// 渲染对应的路由组件
function render() {
let pathname = location.pathname;
routerView.innerHTML = router[pathname]
}
// 页面锚点发送变化
window.addEventListener('popstate', () => {
render()
})
// 页面加载
window.addEventListener('DOMContentLoaded', () => {
listeners()
// 渲染组件
render()
})
</script>
</html>
复制代码
在目录下运行:
light-server -s . --historyindex '/history.html' --port 3000 //模拟真实服务器找到该资源重定向到index.html(初始页面)
复制代码
相当于nginx配置:
try_files $uri $uri/ /index.html
复制代码
image.png
然后打开要你命3000,http://localhost:3000[8]查看效果
简单说一下这个history原理模拟
做了些什么:
render函数
负责渲染pathname
对应路由组件
listeners
负责给每一个a标签
绑定一个click事件
,该事件:
利用pushstate
把路由地址的pathname
修改为对应a标签的href值
render
渲染对应的路由
e.preventDefault()
阻止默认操作(阻止a标签来实现跳转)
popstate
在使用浏览器前进后,退时触发render
DOMContentLoaded
页面加载时负责给a标签
注册事件并且渲染首页对应的路由组件
注意:
e.preventDefault()
这个默认操作很重要,不阻止默认就会重新向服务器发送请求,使页面刷新pushstate
无论带不带锚点,都会加入历史记录hashchange
,监听该事件,当哈希值发生改变触发相应的回调:render
路由渲染(因为不论是手动更改url地址还是浏览器前进后退还是内部封装的方法push,router-link跳转,只要带有锚点"#"改变都会触发hashchange
)pushstate
以及popstate
,当手动更改路由就会重定向到index.html再次触发初始化事件,当使用router-link跳转或内部封装的push方法都要用pushstate
去更改url地址并且手动触发render
路由渲染,popstate
则监听浏览器前进后退,然后触发render
hash(核心hashchange
):
手动改变url(hashchange->render)
router-link跳转,push跳转(hashchange->render)
浏览器前进后退(hashchange->render)
初始化render
所有跳转都是hashchange->render
history(核心pushstate,popstate
):
手动改变url(触发重定向->初始化render)
router-link跳转,push跳转(pushstate更改url,添加历史记录,调用render)
浏览器前进后退(popstate->render)
初始化render
跳转情况:
image.png
看到这里这里我知道你看懂了,给个反应,点赞评论over,over...
history模式
的原理去模拟hash模式
(原生及vue版)理解了核心,了解了流程,那就开始模拟吧
historyToHash.html:
<!DOCTYPE html>
<html lang="en">
<body>
<a href="/">home</a>
<a href="/about">about</a>
<a href="/404">404</a>
<!-- 渲染路由对应的组件 -->
<div id="routerView"></div>
</body>
<script>
let routerView = document.querySelector('#routerView')
let router = {
'/': 'homeComponent',
'/about': 'aboutComponent',
'/404': '404Component'
}
function listeners() {
let aDoms = document.getElementsByTagName('a')
Array.from(aDoms).forEach(el => el.addEventListener('click', function (e) {
history.pushState(null, '', `#${el.getAttribute('href')}`)
render()
})
)
}
//渲染对应的路由组件
function render() {
let hash = location.hash.substr(1);
routerView.innerHTML = router[hash]
}
//页面锚点发送变化
window.addEventListener('popstate', () => {
render()
})
//页面加载
window.addEventListener('DOMContentLoaded', () => {
//当输入url不带#的给网页加#
location.hash || (location.hash = "/")
listeners()
//渲染组件
render()
})
</script>
</html>
复制代码
在目录下运行:
light-server -s . --historyindex '/historyToHash.html' --port 3000 //模拟真实服务器找到该资源重定向到index.html(初始页面)
复制代码
然后打开要你命3000,http://localhost:3000[9]查看效果
新建一个historyToHashRouter.js文件,装载模拟的VueRouter类
let _Vue = null
export default class VueRouter {
static install(Vue) {
// 1 判断当前插件是否被安装
if (VueRouter.install.installed) {
return
}
VueRouter.install.installed = true
// 2 把Vue的构造函数记录在全局
_Vue = Vue
// 3 把创建Vue的实例传入的router对象注入到Vue实例
// _Vue.prototype.$router = this.$options.router
_Vue.mixin({
beforeCreate() {
if (this.$options.router) {
_Vue.prototype.$router = this.$options.router
}
}
})
}
constructor(options) {
this.options = options
this.routeMap = {}
// observable 把数据改为响应式
this.data = _Vue.observable({
current: ''
})
this.init()
}
init() {
this.createRouteMap()
this.initComponent(_Vue) // 初始化router-link,router-view
this.initEvent() // 相当于原声版的初始事件
}
createRouteMap() {
// 遍历所有的路由规则 吧路由规则解析成键值对的形式存储到routeMap中
this.options.routes.forEach(route => {
this.routeMap[route.path] = route.component
})
}
initComponent(Vue) {
Vue.component('router-link', {
props: {
to: String
},
render(h) {
return h('a', {
on: {
click: this.clickhander
}
}, [this.$slots.default])
},
methods: {
clickhander(e) {
// 历史模式
history.pushState(null, '', `/#${this.to}`)
this.$router.data.current = this.to // 相当于原生版的render
}
}
// template:"<a :href='to'><slot></slot><>"
})
const self = this
Vue.component('router-view', {
render(h) {
// self.data.current
const cm = self.routeMap[self.data.current]
return h(cm)
}
})
}
initEvent() {
// 历史模式
// 当输入url不带#的给网页加#
location.hash || (location.hash = "/")
this.data.current = location.hash.substr(1) // 相当于原生版的render
window.addEventListener('popstate', () => { // 监听浏览器前进后退
this.data.current = location.hash.substr(1) // 相当于原生版的render
})
}
}
复制代码
history模式
的原理去模拟hash模式
,可以使用hashchange
事件监听路由地址的变化终于,来到这里,我也看懂了题目了,整:
let _Vue = null
export default class VueRouter {
static install(Vue) {
// 1 判断当前插件是否被安装
if (VueRouter.install.installed) {
return
}
VueRouter.install.installed = true
// 2 把Vue的构造函数记录在全局
_Vue = Vue
// 3 把创建Vue的实例传入的router对象注入到Vue实例
// _Vue.prototype.$router = this.$options.router
_Vue.mixin({
beforeCreate() {
if (this.$options.router) {
_Vue.prototype.$router = this.$options.router
}
}
})
}
constructor(options) {
this.options = options
this.routeMap = {}
// observable
this.data = _Vue.observable({
current: ''
})
this.init()
}
init() {
this.createRouteMap()
this.initComponent(_Vue)
this.initEvent()
}
createRouteMap() {
// 遍历所有的路由规则 吧路由规则解析成键值对的形式存储到routeMap中
this.options.routes.forEach(route => {
this.routeMap[route.path] = route.component
})
}
initComponent(Vue) {
Vue.component('router-link', {
props: {
to: String
},
render(h) {
return h('a', {
on: {
click: this.clickhander
}
}, [this.$slots.default])
},
methods: {
clickhander(e) {
// 历史模式
history.pushState(null, '', `/#${this.to}`)
this.$router.data.current = this.to // 相当于render,pushState不会触发hashchange
}
}
// template:"<a :href='to'><slot></slot><>"
})
const self = this
Vue.component('router-view', {
render(h) {
// self.data.current
const cm = self.routeMap[self.data.current]
return h(cm)
}
})
}
initEvent() {
// hashchange
//当输入url不带#的给网页加#
location.hash || (location.hash = "/")
this.data.current = window.location.hash.substr(1) || '/'
window.addEventListener('hashchange', () => {
this.data.current = window.location.hash.substr(1) || '/'
})
}
}
复制代码
pushstate
popstate
没有使用,因为hashchange
同样也可以监听到浏览器的前进后退(只要url带有锚点"#")我的个人猜想:
因为dev-server做不到页面重定向的效果,真正的history需要配置nginx重定向
因此使用hashchange来模拟这种效果
一提到源码,你们就这样,别慌,我们这次只是简单看看,粗略粗略看一下(vue-router版本3.1.6):
image.png
不知道你们还记得本文开头提到过的:
但是你有没有想过?通过
history.pushState
改变地址栏与监听popstate
事件并不是history的专利
,hash模式
也能使用
// index.js
export default class VueRouter {
...
constructor (options: RouterOptions = {}) {
...
switch (mode) {
case 'history': // 历史模式
this.history = new HTML5History(this, options.base)
break
case 'hash': // 哈希模式
this.history = new HashHistory(this, options.base, this.fallback)
break
case 'abstract':
this.history = new AbstractHistory(this, options.base)
break
default:
if (process.env.NODE_ENV !== 'production') {
assert(false, `invalid mode: ${mode}`)
}
}
}
}
复制代码
有没有一点疑惑?这个pushState
,有点东西?哈希模式难道也用历史模式的东西?
// hash.js
function pushHash (path) {
if (supportsPushState) {
pushState(getUrl(path))
} else {
window.location.hash = path
}
}
export class HashHistory extends History {
...
push (location: RawLocation, onComplete?: Function, onAbort?: Function) {
const { current: fromRoute } = this
this.transitionTo(
location,
route => {
pushHash(route.fullPath)
handleScroll(this.router, route, fromRoute, false)
onComplete && onComplete(route)
},
onAbort
)
}
setupListeners () {
...
// 你疑惑了吗?,也用popstate
const eventType = supportsPushState ? 'popstate' : 'hashchange'
window.addEventListener(
eventType,
handleRoutingEvent
)
this.listeners.push(() => {
window.removeEventListener(eventType, handleRoutingEvent)
})
}
}
复制代码
// html5.js
export class HTML5History extends History {
...
push (location: RawLocation, onComplete?: Function, onAbort?: Function) {
const { current: fromRoute } = this
this.transitionTo(location, route => {
pushState(cleanPath(this.base + route.fullPath))
handleScroll(this.router, route, fromRoute, false)
onComplete && onComplete(route)
}, onAbort)
}
setupListeners () {
...
window.addEventListener('popstate', handleRoutingEvent)
this.listeners.push(() => {
window.removeEventListener('popstate', handleRoutingEvent)
})
}
}
复制代码
他们使用的pushState
方法都是一样的;
相信聪明的你已经从中发现了端倪:
向下兼容
:当浏览器不支持history的时候可以使用hash"#"
的区别请你一定一定要先说明它们的核心
,说你的理解
,再说源码
,别真憨憨说只有"#"
,然后不作任何解释本文由哈喽比特于3年以前收录,如有侵权请联系我们。
文章来源:https://mp.weixin.qq.com/s/9w0b5YxxX1yCln5SzYLVTw
京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。
日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。
据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。
今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。
日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。
近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。
据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。
9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...
9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。
据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。
特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。
据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。
近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。
据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。
9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。
《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。
近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。
社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”
2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。
罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。