Vue常规面试题
5390字约18分钟
2024-07-09
一、对Vue的理解
vue的核心以数据驱动的模型,即mvvm模型驱动数据,在以数据驱动模型 支持组件化思想,每个vue文件都可以看作一个组件,其优点是灵活高复用性,可以实现高内聚,低耦合,提高可维护性 使用模板template作为语法,引入很多诸如v-if、v-for、v-prev等指令,用于操作视图 vue是伪单向数据流,父子之间可以通过$parent
和$children
获取到内容 vue是渐进式框架,官方对状态管理,路由等方面提供了一揽子解决方案,便于学习,如果项目中用到vue-router就用,不用也没关系
二、spa单页面的理解
spa就是单页面应用,是一种网页类型,他只有一个主页面,有多个页面片段,他是通过两种路由形式hash、history路由变化实现页面动态渲染改变内容 spa只有一个主页面,所以对于seo优化非常不方便,但是能显著提升用户的体验,虽然第一次加载需要加载大量的资源,但是后续的内容交互只需要加载以缓存的内容不需要再向服务器发送请求,所以页面会非常迅速。 由于是使用单页面,所有的交互问题只需要在前端完成就好,想要要数据就向后端发送请求,有利于前后端分离,更容易维护和迭代 如何给spa做seo优化
- 使用ssr服务器渲染
- 使用phantomjs针对爬虫处理 如何使spa单页面首屏加快
- 缩小入口文件的大小
- 静态文件本地缓存
- ui框架按需加载
- css、js、图片资源压缩处理
- 使用矢量图或者icon图标用字体
三、v-show和v-if的区别
v-show是无论如何,都会将dom节点渲染出来,然后再判断需不需要显示出来,所以v-show是能在dom树中找到的 v-if是先判断是否需要渲染,如果为false就直接跳过渲染,所以在真实的dom树中是找不到的 v-show一般用于多复用,反复使用的场景,减少dom渲染的次数,只需要修改display v-if一般用于单次,少复用的场景
四、Vue的渲染流程(vue的挂载流程)
- 首先vue会进入init阶段,遇到mixin等方法会将mixin的内容加载到大Vue中
- 进入实例化阶段
- 首先vue执行数据初始化,会将大Vue的全局属性mixin等方法合并到自己的实例,data就合并,生命周期就转化为数组的信息保存在实例中
- 执行生命周期钩子 BeforeCreate
- 执行初始化state操作,将data中的属性添加get和set属性,数组就重写七个方法
- 执行生命周期钩子 Created
- 判断是否有render,render是最高优先级,如果没有就找template最后找el,el就将el获取到并转化为字符串
- 解析字符串,将字符串转化为树形结构,再通过树形结构将字符传转化为函数字符串,通过with+function的方式改变this,并将函数此挂载到实例的render上
- 执行生命周期钩子 BeforeMount
- 将自己包装成watch后执行render方法,render会向data去取值,取值的时候触发get函数,将自己存储在属性的dep上,这样set触发的时候就能通过遍历执行render方法实现双向绑定,期间如果遇到子组件就执行子组件完整的生命周期
- 一切都准备好之后,render执行会生成真实的dom节点,这时候只需要将真实的dom节点挂载到app下
- 执行生命周期钩子 Mounted
五、生命周期的使用
- beforeCreate:组件实例被创建之前使用,这时候是拿不到data的
- created:组件实例被创建,这时候已经有了data,data中也支持双向绑定,可以对于data进行操作
- beforeMount:组件被挂载之前调用,这时候是拿不到dom节点的
- mounted:组件挂载完成,组件可以拿到真实的dom节点
- beforeUpdate:更新dom节点之前执行
- updated:dom节点更新之后
- beforeDistory:组件实例销毁之前
- distoryed:组件销毁之后,用于清除定时器
- actived:keep-alive激活时
- deactived:keep-alive组件被停用时
- errorCaptured:子孙组件发生错误时,用于ui降级,不至于看到白屏 Vue3的生命周期
- onBeforeMount:挂载之前
- onMounted:组件挂载之后
- onBeforeUpdate:更新之前
- onUpdated:更新之后
- onBeforeUnmount:卸载之前
- onUnmounted:组件卸载之后
- renderTracked:render被调用时触发,首次被调用是也触发
- renderTriggerd:render被调用时触发,首次被调用时不会触发
六、Vue中传参方式
- 通过props和emit传参
$parent
和$children
直接修改值- eventBus创建vue实例
- 使用ref直接修改内容
- attrs和listeners
- provide和inject用于祖孙间传参
- vuex
七、Vue双向绑定原理
- 当state初始化的时候,会递归使用defineproperty为对象的属性都添加get和set属性,遇到数组就不适用defineproperty而是将数组的七个方法重写,使用切片编程的方式,先改变数组,然后调用render方法
- vue在取值之前会将自己包装成watcher,放在全局的Dep.target上,当取值的时候,属性会将watcher放在自己的dep上
- 当值发生改变的时候,调用dep上的更新方法,dep就通知所有的watcher执行更新操作,即render方法 vue3使用的是proxy + reflect方法,给对象添加代理,当然proxy也是不能监听深层次的内容的,还是需要递归
八、slot的使用
有三种插槽
- 默认插槽
- 具名插槽
- 作用域插槽 作用就是封装组件更加灵活
九、Vue常用的修饰符
- 表单修饰符 lazy,trim、number
- 事件修饰符 stop、prevent、self、once、capture、native
- 按键修饰符 left、right、keydown、enter 其他修饰符
- sync
十、封装的自定义指令
v-copy,将数据传入当点击的时候,就生成一个textare然后选中里面的内容,调用navgitor的copy方法将数据写到copy中 v-has,校验权限,当传入一个当前按钮所需要的权限大小,后台在inserted的时候校验权限,如果权限够就保留,如果权限不够就调用 el.parentNode.removeChild(el)将按钮删除 v-cd,节流、当点击按钮的时候会将按钮变为不可点击状态,开启一个定时器,当事件到的时候将按钮改为可点击状态
十一、封装axios
封装axios
- 使用axios.create传入options比如timeout、baseUrl、withCredentials等,baseURl通过当前的环境变量中获取,两个文件,.env.production,.env.development 可以在里面定义环境变量,通过process.env获取
- 定义 interceptors.request.use定义请求拦截,这里可以做一些添加 / 校验token的操作,还有进度条,重置请求等
- 定义 interceptors.response.use定义响应拦截,这里可以对一些错误的状态码进行拦截操作,比如没有权限,未登录,请求错误等处理,然后返回错误的promise 封装axios的终止方法 定义一个方法用于根据config的baseUrl、url、params、query,method拼接字符串 key 定义一个map容器用于将保存key的终止请求方法 定义方法add,当发起请求的时候首先终止前一个请求,然后将请求以key、value的形式放到容器中,key为生成的key,value为new axios.cancelToken执行回调函数传入的cancel,然后将config上添加cancelToken方法
config.cancelToken = new axios.CancelToken((cancel) => {
if (!pendingMap.has(url)) pendingMap.set(url, cancel)
})
定义方法remove,当调用时,remove时传入config执行他的cancel方法 拓展终止所有的请求,就算将map中的所有内容,都拿出来执行一次
十二、ssr
ssr即服务端渲染,页面不是返回没处理的单页面,而是在服务器渲染完成的数据
- 有利于seo优化
- 首屏渲染速度加快,用户不用等所有的css加载完成之后才能看到页面,但是这样压力就会来到服务器这边,因为服务器要承担发送和计算的工作
- 项目复杂度会上升
- 每次用户发送请求的时候,服务器会计算之后发送一个新的vue实例
十三、Vue权限管理怎么做
权限管理分为
- 接口权限
- 按钮权限
- 菜单权限
- 路由权限 接口权限一般是使用上传token 的操作,就是在路由请求拦截中,传入token,为了安全我们还传入了时间戳和经过时间戳计算的token
time:xxxx,
token:xxxxx,
matchToken: token + time加密计算,可以实现动态计算,即使复制了token也是没有用的
按钮权限一般使用自定义全局指令做,使用时传入这个按钮所需要的权限,绑定的时候查看这个按钮权限够不够,够的话就保留,如果不够就删除 菜单权限一般是后端返回菜单的列表,然后前端通过动态计算获得 路由权限是 根据后端返回的菜单列表,在路由全局守卫里这么做
- 判断去的页面是否需要登录,如果不需要直接放行
- 需要登录,看看vuex中的登录状态
- true,登录状态
- false,没有登录去登录
- null,不知道有没有登录,就发送一次请求看看登录了没有
- 校验登录完成之后,后端会返回新的路由权限表,如果权限通过就直接通行,如果权限没通过就去登录 这里我们通过菜单绑定路由权限的方式,生成权限表的时候也动态计算了权限菜单,所以如果别的地方给你提高了权限,只需要刷新一下就能显示
十四、开发过程中怎么解决跨域问题的
有三种方案插件、代理、nginx反向代理 第一种是cors的chorme插件,这个方法简单粗暴也比较实用,但是也有缺陷,会造成一些网站不能登录的问题 第二种是配置vue.config.js中的devServer字段,配置target、rewrite等字段,实现代理,vue3的vite是配置vite.congig.js中的server字段 第三种是配置nginx反向代理,在nginx的cofig的配置文件中,配置server字段在这里可以配置监听端口,路径等信息,配置location实现nginx反向代理
十五、Vue部署的404问题
history模式下,客户端会发送请求,要这个页面的数据,但是这个是单页面应用,后端是没有数据的,所以出现404的情况,解决方式是后端收到404请求再将路由重定向到index页面
十六、Vue项目中的错误怎么处理
一般来说页面发生错误就会一层一层向上蔓延,最终到达app所以需要在顶层定义错误边界,使用errorcapture捕获,如果捕获到了错误,就执行响应的处理,然后返回false阻止错误向上蔓延,表示这个错误已经解决,可以忽略
十七、watch实现立即调用和深度监听
立即调用:immediate 深度监听:deep
十八、vue2和vue3的区别
- vue3在性能上做了很大的优化,他在编译的时候跳过静态节点,即上面不需要交互和没有指令的dom节点会直接跳过
- 在vue2中的diff需要手动添加v-pre和v-once等跳过节点,不添加还是会进行编译
- 在vue3中的diff不需要手动添加v-pre和v-once指令就能实现自动跳过编译静态节点
- 文档碎片
- vue2需要在template中添加根节点
- vue3进行优化,自动在最外层添加template
- 编程方式不同
- vue2使用配置型api,类的编程方式
- vue3使用react的hooks式写法,函数式编程,聚合式api可以忽略this的问题
- 底层差距很大 响应式不同
- vue2的响应式是使用defineproperty对对象的每个属性都进行监听,对数组重写方法
- vue3的响应式是使用proxy + refect的方式,只需要对对象进行监听就可以了,而且对数组有更好的支持 diff算法不同
- vue2是先进行头头对比、尾尾对比、头尾对比、尾头对比,然后再将对比没有完成的内容放在一个数组中进行遍历然后替换
- vue3是也同样进行这样的对比,不过vue3在此做了优化,他是使用贪心算法 + 二分法 + 最长递增子序列的方式实现对比替换
- 生命周期不同
- vue2的生命周期
- beforeCreate
- created
- beforeMount
- mounted
- beforeUpdate
- updated
- beforedestory
- destoryed
- vue3的生命周期 删除了 beforeCreate和created beforeDestory和destory改成beforeUnmount和unmounted 增加两个新的周期函数
十九、Vue路由原理
路由分为两种 hash和history hash的原理是当页面的hash值发生改变的时候,页面不会发生重新加载的机制实现,通过手动修改hash值实现页面调换,通过监听hashchange实现组件动态渲染 history的原理是通过html5的新api,通过replacestate和pushstate两个api,当然后通过popstate监听路由的变化实现组件动态渲染,但是history需要后端的配合,因为页面在输入网址后,如果不是index页面,后台会报404的错误,所以需要将所有的请求的重定向到index上交由前端统一处理
二十、Vue的导航守卫有哪些
三个全局导航守卫:
- router.beforeEach 全局前置守卫
- router.beforeResolve 全局解析守卫
- router.afferEach 全局后置守卫 一个路由独享守卫
- beforeEnter 三个路由独享守卫
- beforeRouteLeave:在失活组件离开时触发
- beforeRouteUpdate:在重用组件,
- beforeRouteEnter:在进入对应组件创建之前调用
二一、路由懒加载
使用箭头函数 + import 如果想要实现冬天加载需要拼接字符串不能完全指定动态地址 需要指定地址比如 () => import (
@/pages/${path})
二二、路由传参
有两种方式:params和query params是通过对象的形式传入,通过this.$route.params
获取值,在当前页面刷新的话,会丢失数据 query可以通过对象的形式传入,通过this.$route.query
获取值,他的内容会在路由上通过问号的形式保存在路径上,所以在当前页面刷新不会丢失数据
二三、路由生命周期钩子流程
第一次进入
- 触发调用router.push准备离开页面
- 触发生命周期钩子beforeRouteLeave,这里可以做确认保存等操作
- 调用全局前置守卫beforeEach,这里可以做权限校验等
- 调用路由独享守卫 beforeEnter
- 解析组件
- 在将要进入的路由组件中调用beforeRouteEnter
- 调用全局解析守卫 beforeResolve
- 导航被确认
- 调用全局后置守卫 afterEach
- 触发dom更新
- 调用beforeRouteEnter守卫
如果定义了keep-alive 由于定义了keep-alive所以不会触发组件路由守卫那么顺序为
- 调用beforeRouterLeave离开失活路由
- 调用失活路由的deactive
- 调用全局前置守卫beforeEach
- 调用路由独享守卫beforeEnter
- 调用全局解析守卫beforeResolve
- 调用全局后置守卫afterEach
- 触发dom更新
二四、Vuex的原理
原理其实和vue的双向绑定一样,当实例化的时候会将实例包装成watcher,给仓库的state添加getter和setter属性,于是访问的时候添加到dep中,更新的时候就拿出dep中所有的watcher执行update
二五、Vuex的几个功能分别是什么,怎么使用
- state用于保存全局的状态,组件通过
this.$store.state.xxxx
就能直接过去到,或者使用mapState结构到data中 - mutation中保存了用于唯一修改state的方法,组件使用
this.$store.commit("名称",参数)
,也可以使用mapMutation将其解构到methods中,直接调用 - actions中保存了用于处理异步的方法,组件使用
this.$store.dispatch
("名称",参数) ,也可以使用mapAtions将其解构到methods中,直接调用即可,这里需要注意的是如果actions中的方法想要修改state还需要调用mutation的方法 - getter相当于计算属性,通过
this.$store.getters.xxx
获取值,可以使用mapGetters解构到computed中 - namespace用于模块化的命名空间防止冲突
- modules用大项目的模块化
- plugin用于插件,如果企业级缓存state的插件,vue-persistedstate可以实现本地化存储
二六、如何封装组件
这里封装组件提供两种方式
- 第一种是声明式的,需要引入组件,然后在components中注册,就算父子组件,这里需要注意的是子组件必须要考虑插槽、传参、复用、还有使用
$attrs
实现子组件继承父组件,$listener
实现父组件监听子组件,常用的封装是对于ui组件的再封装,比如我觉得ui组件的样式不好看,但是我还是想要使用他的功能,那么就通过再封装的方式。 封装的组件示例 封装过提交组件,因为有时候页面中新增和修改用的是同一套弹窗,有可能没有图片,有可能图片,所以我对这个做了封装,只需要使用插槽将需要提交的文本框传入就好,交互就用props和emit 封装过点击修改文字的插件,就是在文字后面有个修改按钮,当点击的时候,文字自动切换为输入框,然后修改完成之后只需要点击确认就可以实现文字的修改,slot插槽传入文字,props和emit实现点击提交和点击修改
第二种是函数调用方式的,使用extends这个api,Vue.extends(组件)就会返回一个构造函数,new 之后返回一个实例,执行$mount
可以手动挂载生成el真实的dom节点 封装的组件示例 封装全局登录的dialog,因为如果登录再跳转到首页的话有的东西不好保存,也影响体验,于是封装了一个全局登录的dialog,具体流程如下
- 首先按照往常,写vue的dialog,支持关闭,确定,取消,登录逻辑
- 封装dialog类,创建真实的dom节点作为容器,挂载在body下
- 使用Vue.extends执行生成构造函数,实例化并手动挂载,于是就有了组件的实例,在实例上找到el并append到容器中,通过操作属性就可实现关闭和开启
- 将关闭和开启的函数抽离出来并保存,再使用切片的方式执行传递进去的函数,然后再执行原本的开启关闭函数,这里需要注意this指向的问题
- 为了实现仿element-ui的promise写法,需要将方法返回promise,当点击取消或者取消就调用resolve和reject函数
class Dialog {
constructor() {
this.dialog = document.createElement('div')
document.body.appendChild(this.dialog)
this.initConfirm()
this.initToast()
}
initConfirm() {
const confirmComponent = Vue.extend(confirm)
this.$confirm = new confirmComponent()
this.$confirm.$mount()
this.dialog.appendChild(this.$confirm.$el)
}
initToast() {
const toastComponent = Vue.extend(toast)
this.$toast = new toastComponent()
this.$toast.$mount()
this.dialog.appendChild(this.$toast.$el)
}
confirm(options) {
// 打开弹窗
this.$confirm.$data['isShow'] = true
Object.keys(options).forEach((key) => {
if (!this.$confirm.$data[key]) return
this.$confirm.$data[key] = options[key]
})
return new Promise((resolve, reject) => {
// 返回一个promise 如果点击了取消就触发reject, 如果点击确定就触发resolve
// 使用切片编程
let offStartFn = this.$warn.offFn
let onStartFn = this.$warn.onFn
this.$warn.offFn = () => {
// 需要执行原本的内容
offStartFn.call(this.$warn)
reject()
}
this.$warn.onFn = () => {
onStartFn.call(this.$warn)
resolve()
}
})
}
success(text) {
// 成功的轻提示
this.$toast.setLi({ id: this.$toast.$data.num++, content: text, state: 'success' })
}
error(text) {
this.$toast.setLi({ id: this.$toast.$data.num++, content: text, state: 'error' })
}
warn(text) {
this.$toast.setLi({ id: this.$toast.$data.num++, content: text, state: 'warn' })
}
}
最后一步就是将实例挂载到vue的prototype上就可以实现 this.dialog.login(options).then()
做一些操作了
二七、vue怎么做服务端渲染(ssr)
- 在服务端新建一个服务,用于监听客户端发送的请求
- 当页面发起请求时,找到对应的页面,对应页面执行renderTostring,将vue实例转化为html传给客户端
- 客户端接收到之后将html转化为dom节点,然后替换和复用 客户端有个激活的操作,以便在接收到html时,能够解析出真实的dom节点 使用到的函数方法是createSSRApp