e.preventDefault()
history.pushState(null, ‘’, el.getAttribute(‘href’))
routerView.innerHTML = location.pathname
}))
}
解释下上面代码,其实也差不多:
-
我们通过a标签的href属性来改变URL的path值(当然,你触发浏览器的前进后退按钮也可以,或者在控制台输入history.go,back,forward赋值来触发popState事件)。这里需要注意的就是,当改变path值时,默认会触发页面的跳转,所以需要拦截
<a>
标签点击事件默认行为, 点击时使用 pushState 修改 URL并更新手动 UI,从而实现点击链接更新 URL 和 UI 的效果。 -
我们监听popState事件。一旦事件触发,就改变routerView的内容。
-
load事件则是一样的
有个问题:hash模式,也可以用history.go,back,forward
来触发hashchange事件吗?
A:也是可以的。因为不管什么模式,浏览器为保存记录都会有一个栈。
我们先利用vue-cli建一个项目
删除一些不必要的组建后项目目录暂时如下:
已经把项目放到 github:https://github.com/Sunny-lucking/howToBuildMyVueRouter 可以卑微地要个star吗。有什么不理解或者什么建议,欢迎下方评论
我们主要看下App.vue,About.vue,Home.vue,router/index.js
代码如下:
App.vue
Home |
About
router/index.js
import Vue from ‘vue’
import VueRouter from ‘vue-router’
import Home from ‘…/views/Home.vue’
import About from “…/views/About.vue”
Vue.use(VueRouter)
const routes = [
{
path: ‘/home’,
name: ‘Home’,
component: Home
},
{
path: ‘/about’,
name: ‘About’,
component: About
}
]
const router = new VueRouter({
mode:“history”,
routes
})
export default router
Home.vue
这是Home组件
About.vue
这是about组件
现在我们启动一下项目。看看项目初始化有没有成功。
ok,没毛病,初始化成功。
现在我们决定创建自己的VueRouter,于是创建myVueRouter.js文件
目前目录如下
再将VueRouter引入 改成我们的myVueRouter.js
//router/index.js
import Vue from ‘vue’
import VueRouter from ‘./myVueRouter’ //修改代码
import Home from ‘…/views/Home.vue’
import About from “…/views/About.vue”
Vue.use(VueRouter)
const routes = [
{
path: ‘/home’,
name: ‘Home’,
component: Home
},
{
path: ‘/about’,
name: ‘About’,
component: About
}
];
const router = new VueRouter({
mode:“history”,
routes
})
export default router
先抛出个问题,Vue项目中是怎么引入VueRouter。
-
安装VueRouter,再通过
import VueRouter from 'vue-router'
引入 -
先
const router = new VueRouter({...})
,再把router作为参数的一个属性值,new Vue({router})
-
通过Vue.use(VueRouter) 使得每个组件都可以拥有store实例
从这个引入过程我们可以发现什么?
- 我们是通过new VueRouter({…})获得一个router实例,也就是说,我们引入的VueRouter其实是一个类。
所以我们可以初步假设
class VueRouter{
}
- 我们还使用了Vue.use(),而Vue.use的一个原则就是执行对象的install这个方法
所以,我们可以再一步 假设VueRouter有有install这个方法。
class VueRouter{
}
VueRouter.install = function () {
}
到这里,你能大概地将VueRouter写出来吗?
很简单,就是将上面的VueRouter导出,如下就是myVueRouter.js
//myVueRouter.js
class VueRouter{
}
VueRouter.install = function () {
}
export default VueRouter
Vue.use(plugin);
(1)参数
{ Object | Function } plugin
(2)用法
安装Vue.js插件。如果插件是一个对象,必须提供install方法。如果插件是一个函数,它会被作为install方法。调用install方法时,会将Vue作为参数传入。install方法被同一个插件多次调用时,插件也只会被安装一次。
关于如何上开发Vue插件,请看这篇文章,非常简单,不用两分钟就看完:如何开发 Vue 插件?
(3)作用
注册插件,此时只需要调用install方法并将Vue作为参数传入即可。但在细节上有两部分逻辑要处理:
1、插件的类型,可以是install方法,也可以是一个包含install方法的对象。
2、插件只能被安装一次,保证插件列表中不能有重复的插件。
(4)实现
Vue.use = function(plugin){
const installedPlugins = (this._installedPlugins || (this._installedPlugins = []));
if(installedPlugins.indexOf(plugin)>-1){
return this;
}
const args = toArray(arguments,1);
args.unshift(this);
if(typeof plugin.install === ‘function’){
plugin.install.apply(plugin,args);
}else if(typeof plugin === ‘function’){
plugin.apply(null,plugin,args);
}
installedPlugins.push(plugin);
return this;
}
1、在Vue.js上新增了use方法,并接收一个参数plugin。
2、首先判断插件是不是已经别注册过,如果被注册过,则直接终止方法执行,此时只需要使用indexOf方法即可。
3、toArray方法我们在就是将类数组转成真正的数组。使用toArray方法得到arguments。除了第一个参数之外,剩余的所有参数将得到的列表赋值给args,然后将Vue添加到args列表的最前面。这样做的目的是保证install方法被执行时第一个参数是Vue,其余参数是注册插件时传入的参数。
4、由于plugin参数支持对象和函数类型,所以通过判断plugin.install和plugin哪个是函数,即可知用户使用哪种方式祖册的插件,然后执行用户编写的插件并将args作为参数传入。
5、最后,将插件添加到installedPlugins中,保证相同的插件不会反复被注册。(~~让我想起了曾经面试官问我为什么插件不会被重新加载!!!哭唧唧,现在总算明白了)
第三点讲到,我们把Vue作为install的第一个参数,所以我们可以把Vue保存起来
//myVueRouter.js
let Vue = null;
class VueRouter{
}
VueRouter.install = function (v) {
Vue = v;
};
export default VueRouter
然后再通过传进来的Vue创建两个组件router-link和router-view
//myVueRouter.js
let Vue = null;
class VueRouter{
}
VueRouter.install = function (v) {
Vue = v;
console.log(v);
//新增代码
Vue.component(‘router-link’,{
render(h){
return h(‘a’,{},‘首页’)
}
})
Vue.component(‘router-view’,{
render(h){
return h(‘h1’,{},‘首页视图’)
}
})
};
export default VueRouter
我们执行下项目,如果没报错,说明我们的假设没毛病。
天啊,没报错。没毛病!
install 一般是给每个vue实例添加东西的
在这里就是给每个组件添加$route
和$router
。
$route
和$router
有什么区别?
A:
$router
是VueRouter的实例对象,$route
是当前路由对象,也就是说$route
是$router
的一个属性
注意每个组件添加的
$route
是是同一个,$router
也是同一个,所有组件共享的。
这是什么意思呢???
来看mian.js
import Vue from ‘vue’
import App from ‘./App.vue’
import router from ‘./router’
Vue.config.productionTip = false
new Vue({
router,
render: function (h) { return h(App) }
}).$mount(‘#app’)
我们可以发现这里只是将router ,也就是./router导出的store实例,作为Vue 参数的一部分。
但是这里就是有一个问题咯,这里的Vue 是根组件啊。也就是说目前只有根组件有这个router值,而其他组件是还没有的,所以我们需要让其他组件也拥有这个router。
因此,install方法我们可以这样完善
//myVueRouter.js
let Vue = null;
class VueRouter{
}
VueRouter.install = function (v) {
Vue = v;
// 新增代码
Vue.mixin({
beforeCreate(){
if (this.KaTeX parse error: Expected 'EOF', got '&' at position 9: options &̲& this.options.router){ // 如果是根组件
this._root = this; //把当前实例挂载到_root上
this._router = this.$options.router;
}else { //如果是子组件
this._root= this.KaTeX parse error: Expected 'EOF', got '&' at position 8: parent &̲& this.parent._root
}
Object.defineProperty(this,‘$router’,{
get(){
return this._root._router
}
})
}
})
Vue.component(‘router-link’,{
render(h){
return h(‘a’,{},‘首页’)
}
})
Vue.component(‘router-view’,{
render(h){
return h(‘h1’,{},‘首页视图’)
}
})
};
export default VueRouter
解释下代码:
-
参数Vue,我们在第四小节分析Vue.use的时候,再执行install的时候,将Vue作为参数传进去。
-
mixin的作用是将mixin的内容混合到Vue的初始参数options中。相信使用vue的同学应该使用过mixin了。
-
为什么是beforeCreate而不是created呢?因为如果是在created操作的话,$options已经初始化好了。
-
如果判断当前组件是根组件的话,就将我们传入的router和_root挂在到根组件实例上。
-
如果判断当前组件是子组件的话,就将我们_root根组件挂载到子组件。注意是引用的复制,因此每个组件都拥有了同一个_root根组件挂载在它身上。
这里有个问题,为什么判断当前组件是子组件,就可以直接从父组件拿到_root根组件呢?这让我想起了曾经一个面试官问我的问题:父组件和子组件的执行顺序?
A:父beforeCreate-> 父created -> 父beforeMounte -> 子beforeCreate ->子create ->子beforeMount ->子 mounted -> 父mounted
可以得到,在执行子组件的beforeCreate的时候,父组件已经执行完beforeCreate了,那理所当然父组件已经有_root了。
然后我们通过
Object.defineProperty(this,‘$router’,{
get(){
return this._root._router
}
})
将$router
挂载到组件实例上。
其实这种思想也是一种代理的思想,我们获取组件的$router
,其实返回的是根组件的_root._router
到这里还install还没写完,可能你也发现了,$route
还没实现,现在还实现不了,没有完善VueRouter的话,没办法获得当前路径
我们先看看我们new VueRouter类时传进了什么东东
//router/index.js
import Vue from ‘vue’
import VueRouter from ‘./myVueRouter’
import Home from ‘…/views/Home.vue’
import About from “…/views/About.vue”
Vue.use(VueRouter)
const routes = [
{
path: ‘/home’,
name: ‘Home’,
component: Home
},
{
path: ‘/about’,
name: ‘About’,
component: About
}
];
const router = new VueRouter({
mode:“history”,
routes
})
export default router
可见,传入了一个为数组的路由表routes,还有一个代表 当前是什么模式的mode。因此我们可以先这样实现VueRouter
class VueRouter{
constructor(options) {
this.mode = options.mode || “hash”
this.routes = options.routes || [] //你传递的这个路由是一个数组表
}
}
先接收了这两个参数。
但是我们直接处理routes是十分不方便的,所以我们先要转换成key:value
的格式
//myVueRouter.js
let Vue = null;
class VueRouter{
constructor(options) {
this.mode = options.mode || “hash”
this.routes = options.routes || [] //你传递的这个路由是一个数组表
this.routesMap = this.createMap(this.routes)
console.log(this.routesMap);
}
createMap(routes){
return routes.reduce((pre,current)=>{
pre[current.path] = current.component
return pre;
},{})
}
}
通过createMap我们将
const routes = [
{
path: ‘/home’,
name: ‘Home’,
component: Home
},
{
path: ‘/about’,
name: ‘About’,
component: About
}
转换成
路由中需要存放当前的路径,来表示当前的路径状态
为了方便管理,可以用一个对象来表示
//myVueRouter.js
let Vue = null;
新增代码
class HistoryRoute {
constructor(){
this.current = null
}
}
class VueRouter{
constructor(options) {
this.mode = options.mode || “hash”
this.routes = options.routes || [] //你传递的这个路由是一个数组表
this.routesMap = this.createMap(this.routes)
新增代码
this.history = new HistoryRoute();
}
createMap(routes){
return routes.reduce((pre,current)=>{
pre[current.path] = current.component
return pre;
},{})
}
}
但是我们现在发现这个current也就是 当前路径还是null,所以我们需要进行初始化。
初始化的时候判断是是hash模式还是 history模式。,然后将当前路径的值保存到current里
//myVueRouter.js
let Vue = null;
class HistoryRoute {
constructor(){
this.current = null
}
}
class VueRouter{
constructor(options) {
this.mode = options.mode || “hash”
this.routes = options.routes || [] //你传递的这个路由是一个数组表
this.routesMap = this.createMap(this.routes)
this.history = new HistoryRoute();
新增代码
this.init()
}
新增代码
init(){
if (this.mode === “hash”){
// 先判断用户打开时有没有hash值,没有的话跳转到#/
location.hash? ‘’:location.hash = “/”;
window.addEventListener(“load”,()=>{
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
})
}
}
createMap(routes){
return routes.reduce((pre,current)=>{
pre[current.path] = current.component
return pre;
},{})
}
}
监听事件跟上面原生js实现的时候一致。
前面那我们讲到,要先实现VueRouter的history.current的时候,才能获得当前的路径,而现在已经实现了,那么就可以着手实现$route
了。
很简单,跟实现$router
一样
VueRouter.install = function (v) {
Vue = v;
Vue.mixin({
beforeCreate(){
if (this.KaTeX parse error: Expected 'EOF', got '&' at position 9: options &̲& this.options.router){ // 如果是根组件
this._root = this; //把当前实例挂载到_root上
this._router = this.$options.router;
}else { //如果是子组件
this._root= this.KaTeX parse error: Expected 'EOF', got '&' at position 8: parent &̲& this.parent._root
}
Object.defineProperty(this,‘$router’,{
get(){
return this._root._router
}
});
新增代码
Object.defineProperty(this,‘$route’,{
get(){
return this._root._router.history.current
}
})
}
})
Vue.component(‘router-link’,{
render(h){
return h(‘a’,{},‘首页’)
}
})
Vue.component(‘router-view’,{
render(h){
return h(‘h1’,{},‘首页视图’)
}
})
};
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数前端工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Web前端开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上前端开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:前端)
![](https://i-blog.csdnimg.cn/blog_migrate/9b2e24c1a7420ab00b54e8bc779c09f3.jpeg)
最后
整理面试题,不是让大家去只刷面试题,而是熟悉目前实际面试中常见的考察方式和知识点,做到心中有数,也可以用来自查及完善知识体系。
《前端基础面试题》,《前端校招面试题精编解析大全》,《前端面试题宝典》,《前端面试题:常用算法》PDF完整版点击这里领取
is,‘$router’,{
get(){
return this._root._router
}
});
新增代码
Object.defineProperty(this,‘$route’,{
get(){
return this._root._router.history.current
}
})
}
})
Vue.component(‘router-link’,{
render(h){
return h(‘a’,{},‘首页’)
}
})
Vue.component(‘router-view’,{
render(h){
return h(‘h1’,{},‘首页视图’)
}
})
};
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数前端工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Web前端开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
[外链图片转存中…(img-lwumgwbe-1713318294422)]
[外链图片转存中…(img-v1NdFDW5-1713318294423)]
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上前端开发知识点,真正体系化!
[外链图片转存中…(img-geM9AY2f-1713318294423)]
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:前端)
![](https://i-blog.csdnimg.cn/blog_migrate/9b2e24c1a7420ab00b54e8bc779c09f3.jpeg)
最后
整理面试题,不是让大家去只刷面试题,而是熟悉目前实际面试中常见的考察方式和知识点,做到心中有数,也可以用来自查及完善知识体系。
《前端基础面试题》,《前端校招面试题精编解析大全》,《前端面试题宝典》,《前端面试题:常用算法》PDF完整版点击这里领取
[外链图片转存中…(img-rJUHTnUm-1713318294423)]
[外链图片转存中…(img-35vFpPdh-1713318294424)]
[外链图片转存中…(img-bjEaHY6S-1713318294424)]