1.谈谈你对vue的理解
Vue是一套用于构建用户界面的渐进式框架,核心是数据驱动和组件化开发。
(1)声明式框架
(2)MVVM模式
(3)采用虚拟DOM
(4)区分编译时(打包)和运行时(浏览器)
(5)组件化开发
- 高内聚、低耦合、单向数据流。
- 更新范围,只重新渲染变化的组件。
2.谈谈对SPA的理解
- 只有一个页面,多个页面组件
- 页面切换速度快,用户体验较好,服务端压力小
- 不利于SEO搜索引擎优化
- 首屏渲染时间会相对较长
3.声明式编程和命令式编程
-
声明式编程:
声明式更关注的是结果。就以vue为例,在页面上要展示一个变量,那么就要先定义这个变量,后边通过改变这个变量,来触发页面的刷新。我们只需要结果,vue内部去处理过程,这就是声明式编程
-
命令式编程:
命令式更关注的是过程。例如jquery,想通过点击事件改变页面中的一个元素,那么首先就要获取这个元素,在给元素添加点击事件,执行事件,进而改变页面元素,这种按步骤一步一步操作的,就是命令式编程。
-
命令式编程更为严谨,但是步骤多,开发效率慢。声明式编程开发效率高,可读性相对较差些。
4.谈谈虚拟DOM
- 虚拟DOM把整个DOM树抽象成了一个js对象,这样开发者就可以直接操作对象,而不需要频繁的操作DOM,提升性能。
- 在vue运行时,它将虚拟DOM和真实的DOM树同步,当数据发生变化时,Vue会重新计算虚拟的DOM树,通过diff算法比对差异,查找和标记发生变化的节点,并将他们更新到实际DOM数上。
- 虚拟DOM不依赖于真实平台环境,从而可以实现跨平台。
补充:VDOM是如何做diff的?
- 组件挂载结束后,会记录第一次生成的VDOM -- oldVnode
- 当响应式数据发生变化时,将会引起组件重新渲染,此时就会生成新的VDOM --newVnode
- 使用oldVnode和newVnode做diff操作,将更改的部分应到真实DOM上,从而转换为操作最少量的dom,高效更新视图
5.既然Vue通过数据劫持可以精准探测数据的变化,为什么还需要虚拟DOM进行diff检测差异?
Vue内部设计原因导致的,vue设计的是每个组件一个watcher(渲染watcher),没有采用一个属性对于一个watcher,这样会导致大量watcher的产生,造成内存浪费,如果颗粒度过低也会无法精准检测变化。所以采用diff算法+组件级watcher。
6.对响应式数据的理解
vue2中的对象使用Object.defineProperty对数据进行劫持,给每个属性进行重写,添加getter和setter方法。①当新增属性和删除属性时,无法监控变化,需要通过delete来实现。多层对象则会递归来实现劫持,数组的话会单独处理,通过重写数组的方法来实现。②由于遍历,性能会有些差。③对于map和set的数据也不支持。
vue2的不足之处:① ② ③
vue3则使用proxy来实现数据劫持。
7.Vue如何检测数组的变化
(1)数组考虑到性能原因,没有采用Object.defineProperty来对数组的每一项进行拦截,而是选择重写数组(push、shift、pop、unshift、splice、sort、reverse)方法
(2)数组中如何是对象数据类型也会进行递归劫持。
(3)缺点:数组的索引和长度变化时无法监控到的
8.v-show && v-if
v-show: dom节点渲染,display为none
v-if:dom节点不渲染
9.computed && watch
(1)computed
-
仅当用户取值时才会执行对于的方法
-
计算属性不支持异步逻辑
-
具有缓存作用,依赖的值不变,不会再次执行
原理描述:
- 每一个计算属性内部都维护了一个dirty:true的属性。当dirty为true的时候,就会执行用户的方法,拿到值后把值川村起来this.value,dirty改为false。再次取值,dirty为false,直接返回值。
- 更改数据的时候,dirty会被修改为true,并且会触发更新,页面重新渲染,重新计算计算属性的值
(2)watch
-
监控数据的变化,当值发生变化时候会触发回调
-
监控基本类型的,直接使用。
-
监控对象,可以指定某一个属性,也可以配置deep:true属性监控整个对象
注意点:异步要注意竞态问题,vue3提供了第三个入参onCleanup函数,让用户更加方便的解决清理问题。
10.ref和reactive区别
(1)ref
既可以处理基本类型,也可以处理引用类型。
(2)reactive
处理引用类型的数据。
11.watch 和 watchEffect的区别
(1)watch
接收两个入参,第一个为侦听的对象,第二个为回调函数。
侦听一个或多个响应式数据源,在数据源变化的时候调用一个回调函数
(2)watchEffect
接收一个入参,立即执行,后续的回调函数也是自身。
立即运行一个函数,然后被动地追踪它的依赖,当这些依赖改变时,重新执行该函数
// 1.getter函数 2.cb
watch(
()=>state.name, // 数据变化之后,会执行回调函数
(newValue, oldValue) => {}
)
// getter函数
watchEffect(()=>{
// 数据变化后,会再次执行这个getter函数
app.innerHTML = state.name
})
12.如何将template转换成render函数
vue中含有模板编译的功能,它的主要作用就是将用户编写的remplate编译为js可以执行的render函数。
(1)将template目标转换成ast语法树 - parseHTML
(2)对静态语法做静态标记 - markup diff算法来优化的 静态节点 跳过diff操作
(3)重新生成代码 - codeGen
13. new Vue这个过程中究竟做了什么?
- 在new Vue的时候,会做一些初始化操作
- 内部会初始化组件绑定的事件,初始化组件的父子关系:children $root
- 初始化响应式数据data、computed、props、watch、methods。同时也初始化了provider、inject方法。内部会对数据进行劫持,对象采用defineProperty,数组采用方法重写
- 再看一些用户是否传入了el属性和template或者render。render的优先级更高,如果用户写的是template,会做模板编译,将template转化成render函数
- 内部挂载的时候会产生一个watcher,会调用render函数来触发依赖收集。内部还会给所有的响应式数据增加dep属性,让属性记录当前的watcher(用户后续修改的时候,可以触发watcher重新渲染)
- vue更新的时候采用虚拟DOM的方式,进行diff算法更新
14.Vue.observable有了解过吗?说说看
原理就是创建一个响应式对象,可以实现让组件共享数据,不管是平级 跨级或者父子组件,类似于pinia或者vuex类的状态管理工具。但是有缺陷,数据修改比较随意,开发中用的不多,vue3也不会用这个api。
15.v-if和v-for哪个优先级更高
v-for和v-if应避免在同一个标签里使用,如果遇到这样的场景,可以考虑用计算属性来优化。用计算属性搭配v-if条件,来计算出需要v-for的数据。
在Vue2中,先解析v-for,后解析v-if。会导致循环后再判断,浪费性能。
在Vue3中,v-if的优先级高于v-for
16.生命周期有哪些?
1.vue2的生命周期
主要的生命周期有:创建前后、挂载前后、更新前后、销毁前后
-
beforeCreate:初始化父子关系及事件,数据观测(data observer)之前被调用,一般在编写插件的时候会用到这个钩子函数
-
created:实例已经创建完成后调用,在这一步,实例已经完成了如下配置:数据观测、属性和方法等。但是这时候还没有$el,可以用于发送一些异步请求等
-
beforeMount:在挂载开始之前被调用,相关的render函数首次被调用
-
mounted:el被新建的vm.$el替换,并挂载到实例上去之后调用该钩子函数,可以用于获取dom元素
-
beforeUpdate:数据更新时候被调用,发生在虚拟DOM重新渲染和打补丁之前,此时修改数据不会再次触发更新方法
-
updated:由于数据更改导致的虚拟DOM重新渲染和打补丁,在这之后会调用该钩子函数
-
beforeDestory:实例销毁之前被调用,此时,实例依然可用
-
destoryed:实例销毁后调用,调用后,Vue实例的所有东西都会解绑,事件监听会被移除,所有的子实例也会被销毁。
-
keep-alive(activated和deactivated):组件会被缓存下来
- activated:进入组件就会被调用,可做一些数据更新,初始化数据等操作
- deactivated:离开组件的时候调用,可做一些清除操作
2.Vue2和Vue3的生命周期对比
-
Vue2
- beforeCreate / created / beforeMount / mounted / beforeUpdate / updated / beforeDestory / destoryed / activated / deactivated / errorCapture(捕获一个来自子孙组件的错误时被调用)
-
Vue3
- setup / onBeforeMount / onMounted / onBeforeUpdate / onUpdated / onBeforeUmount / onMounted / onActivated / onDeactivated / onErrorCaptured
Vue3的生命周期都是on开头,新增了组合式API setup, 没了create俩钩子。destory换成了unmounted
17.父组件和子组件的生命周期执行顺序
渲染时候:父组件beforeCreate => 父组件created => 父组件 beforeMount => 子组件beforeCreate - created - beforeMount - mounted => 父组件mounted
更新时候:父组件beforeUpdate => 子组件beforeUpdate - updated => 父组件updated
卸载时候:父组件beforeDestory => 子组件beforeDestory - destoryed => 父组件destoryed
父组件等待子组件完成后,才会执行收尾操作。
18.Vue中diff算法原理
(1)diff概念
vue基于虚拟DOM来做更新。diff的核心就是比较两个虚拟节点的差异,Vue的diff算法是平级比较,不会考虑跨级比较的情况。内部采用深度递归的方式 + 双指针的方式进行比较。
(2)diff比较流程
-
先比较是否有相同节点:key 标签
-
相同节点比较属性,并复用老节点(将老的虚拟dom复用给新的虚拟节点dom)
-
比较子节点,考虑老节点和新节点的子节点的情况。
- 老节点有子节点,新节点没有子节点:直接删除
- 老节点没有子节点,新节点有子节点:直接插入
- 老节点有子节点,新节点有子节点:更新子节点updateChildren
-
优化比较:头头、尾尾、头尾、尾头
-
对比查找进行复用
vue3中采用最长递增子序列来实现diff优化。
- 最长递增子序列:在一个给定的数值序列中,找到一个子序列,使得这个子序列元素的数值依次递增,并且这个子序列的长度尽可能的大。最长递增子序列的元素在原序列中不一定是连续的。
19.Vue中key的作用和原理
(1)key的概念
- key的特殊attribute主要用在vue的虚拟DOM算法,在新旧nodes比对时,辨识VNodes。如果不使用key,Vue会使用一种最大限度减少动态元素,并且尽可能的尝试就地修改/复用相同类型元素的算法
- 当Vue正在更新使用v-for渲染的元素列表时,他默认使用的是“就地更新”的策略。如果数据项的顺序被修改,Vue不会移动DOM元素来匹配数据项的顺序,而是就地更新每个元素,并且确保他们在每个索引位置正确渲染
(2)key的作用
- Vue在patch过程中,通过key可以判断两个虚拟节点是否是相同节点(可以复用老节点)
- 没有key会导致更新的时候出现问题
- 尽量不要使用索引来作为key
20.Vue的use是干什么的?
安装Vue.js插件,如果插件是一个对象,则需要提供install方法。如果是一个函数,它会被作为install方法。install方法调用时,会将Vue作为参数传入,这样插件中就不需要依赖Vue了。
插件的功能:
- 添加全局指令、全局过滤器、全局组件
- 通过全局混入来添加一些组件选项
- 添加Vue实例方法,通过把他们添加到Vue.prototype上实现
let plugin1 = {
install(_vue, ...args){
console.log(_vue, args)
}
}
let plugin2 = (_vue, ...args) => {
console.log(_vue, args)
}
Vue.use(plugin1)
Vue.use(plugin2)
21.谈谈你对nextTick的理解
Vue中视图更新是异步的,使用nextTick方法可以保证用户定义的逻辑在更新之后执行的,可以拿到渲染后的dom元素
多次调用nextTick的话,会被合并执行。
22.Vue的data为什么要是一个函数
- 根实例对象data可以使对象,也可以是函数,不会产生数据污染的情况
- 组件实例对象data必须时函数,目的是为了防止多个组件实例对象之间共用一个data,产生数据污染。所以要通过工厂函数返回全薪的data作为组件的数据源
23.函数组件的优势在哪儿
函数式组件特点:
-
stateless:无状态,不需要管理任何状态
-
instanceless:无实例,也就没有this
-
没有生命周期函数,不需要初始化
-
只接受一些props的函数
-
渲染开销很低,渲染相对较快
-
函数式组件没有this,参数就靠context来传递
- context包括:children、props、parent、data、linstener、injections、slots
// 定义一个函数式组件
export default{
name: 'functional-button',
functional: true,
render(createElement, context){
return createElement('button', context.children)
}
}
// app中引入函数式组件
<template>
<FunctionalButton>
click me
</FunctionalButton>
</template>
24.Vue过滤器的应用场景有哪些?
过滤器不会改变原始数据,它只是对数据进行加工处理后,返回过滤后的数据,最终将过滤后的数据展示在页面上。我们也可以将它理解成是纯函数。
(1)注册过滤器
全局注册:
Vue.filter(name, callback)
局部注册:
new Vue({ filters: { ... } })
(2)使用过滤器
{{ xxx | 过滤器 }} 或 :属性="xxx | 过滤器"
// 过滤器也可以串联:
{{ xxx | 过滤器1 | 过滤器2 }} 或 :属性="xxx | 过滤器 | 过滤器2"
(3)传参
// message是作为第一个参数被传递的,参数1 参数2 分别作为第2 3 参数传递
{{ message | 过滤器('参数1', '参数2') }}
全局和局部命名重复时,以局部为准。
Vue3放弃了过滤器。
常用场景:单位转换、时间格式化、文本格式化等
25.v-once的使用场景有哪些
v-once是Vue的内置指令,只渲染组件和元素一次,随后的重新渲染,元素、组件以及其所有的子节点都将被视为静态内容并跳过。这可以用于优化更新性能。
Vue3.2之后,增加了v-memo指令,通过依赖列表的方式控制页面的渲染。
// 只有在依赖项value1 value2发生变化的时候,内部元素才会重新渲染
<div v-memo="[value1, value2]">
{{ message }}
</div>
26.Vue.mixin的使用场景和原理
mixin可以用来扩展组件,将公共逻辑抽离出来,在需要该逻辑的组件里进行混入,采用策略模式针对不通的属性进行合并。如果混入的数据和组件本身的数据产生冲突,以组件自身的为准。
分为局部混入和全局混入。局部混入一般是复用逻辑,全局混入一般是编写插件。
Vue.mixin(mixin) // 全局混入
new Vue({ mixins: [mixins] }) // 局部混入
合并策略(核心就是对象的合并处理):
- props、methods、inject、computed同名时会被替换
- data会被合并
- 生命周期和watch会被合并成队列
- components(组件)、directives(指令)、filters(过滤器)会在原型链上叠加
27.Vue中slot是如何实现的?什么时候使用它?
插槽是利用slot标签进行占位,在使用组件的时候,组件标签内容会分发到对于的slot中。
通过插槽可以更好的让用户对组件进行扩展和定制化,可以通过name来定义具名插槽,指定渲染的位置。
可以设置后备内容,为插槽指定默认值:我是插槽的默认值
分为:默认插槽、具名插槽,另外还有作用域插槽:让插槽能够访问子组件中才有的数据
28.你对双向绑定的理解以及它实现的原理
Vue中双向绑定靠的是v-model指令,可以将一个动态值绑定到视图上,修改视图上的值,数据对于的值也会同步改变。
组件上的v-model就是value+input事件的语法糖(vue3中为modalValue和update:modalValue的语法糖)
表单上v-model:文本为value+input;复选框为checked+change
问题:如果有多个属性想做双向绑定,怎么办?
vue2不支持。
vue3可以通过如下(通过:改名字):
// v-model 等同于 v-model:modalValue
<my-com v-model='value1' v-model:a='valueA' v-model:b='valueB'></my-com>
29.Vue中.sync修饰符的作用
有时候,我们需要对一个prop进行“双向绑定”,这时可以使用.sync来实现。v-model默认只能双向绑定一个属性,通过.sync修饰符,就可以绑定多个属性
<my-com :属性名.sync="xxx">
my-com组件里通过:$emit('update:属性名', value) 来触发
vue3中 .sync 语法被移除了
30.Vue中递归组件的理解
递归组件就是层层递进,最后拼接到一起,特点就是层级分明。例如组件库里的树组件,其实现就是通过递归的方式。
核心思路:
(1)循环出一级类别
(2)判断如果有多级,再调用自身
每个组件都会有一个name属性,这个name其实就是为了帮助我们实现递归的。
代码逻辑也很简单,重点在我的子组件。但父组件传过来的树形数据结构到子组件后,我们需要拿到数据并做遍历,然后再下一行加入核心逻辑:
if 发现我们有子数据,那么我们直接调用自身组件,也就是直接使用name值做组件声明。最关键的是要把子数据结构再传入我们自身组件,那么我们就成功的实现了数据的层层遍历。
31.组件中写name选项有什么好处?
- 增加name选项会在component属性中增加组件自身,实现组件的递归调用
- 可以标识组件的具体名称,方便调试和查找对应组件
32.最常用的修饰符,有哪些应用场景?
- 表单修饰符:lazy(表单失去焦点或回车再更新) number trim
- 事件修饰符:stop prevent self once capture passive native
- 鼠标按键修饰符:left right middle
- 键值修饰符: 对keycode处理
- 双向绑定修饰符:sync
33.Vue中异步组件的作用和原理
(1)概念
Vue允许你以一个工厂函数的方式,定义你的组件,这个工厂函数会异步解析你的组件定义,在需要的时候再去加载。推荐做法是将异步组件和webpack的code-splitting功能一起搭配使用。
(2)写法
2.1 回调写法
{
components: {
"my-component": (resolve, reject) => {
setTimeout(function(){
resolve({
render(h){
return h('div', 'hello')
}
})
}, 1000)
}
}
}
2.2 promise写法(类似路由懒加载的写法,引入异步组件)
{
components: {
"my-component": ()=>{
import(/*webpackChunkName: "B4"*/ "./component/B4.vue")
}
}
}
2.3 对象写法
const asyncComponent = () => {
// 需要加载的组件,应该是一个Promise
components: import("./myComponent.vue"),
// 加载中展示的组件
loading: LoadingComponent,
// 加载失败时展示的组件
error: ErrorComponent,
// 展示加载时组件的时间,毫秒
delay: 200,
// 超过时间没有加载出,则使用失败的组件。默认值是:Infinity
timeout: 3000
}
(3)异步组件的原理
- 默认渲染异步占位符节点
- 组件加载完毕后,调用$forceUpdate强制更新,渲染加载完毕后的组件
34.动态组件的使用方法
(1)基本用法
// currentTabComponent可以包括已注册组件的名字或者一个组件的选项对象
<component :is="currentTabComponent"></component>
(2)进阶用法
2.1 搭配异步组件,延迟加载
<template>
<div>
<button @click="loadComponent">load component</button>
<component :is="currentComponent"/>
</div>
</template>
<script>
data(){
return { currentComponent:null }
},
methods: {
async loadComponent(){
const component = await import('./componentA.vue')
this.currentComponent = component.default
}
}
</script>
2.2 使用命名视图
使用vue-router时候,需要通过router-view来定义视图出口。有时候需要多个视图出口,渲染不同的内容的时候,我们来可以通过name属性来定义命名视图。
// 定义阶段
<template>
<!-- 默认name为defaut -->
<router-view></router-view>
<!-- 此处渲染Header组件内容 -->
<router-view name="header"></router-view>
<!-- 此处渲染footer组件内容 -->
<router-view name="footer"></router-view>
</template>
// 路由层面配置
routes: [
{
path: '/',
name: 'home',
components: {
header: Header,
footer: Footer,
default: MainBody
}
}
]
2.3 传入动态props
动态组件传递props和其他组件都一样,通过v-bind来动态绑定动态数据。
<component :is="currentTabComponent" :message="message"/>
35.keep-alive平时在哪里使用?
(1)概念
keep-alive是Vue的内置组件,能在组件切换的过程中缓存组件的实例,而不是销毁它们。在组件再次重新激活的时候,可以通过缓存的实例拿到之前渲染的DOM进行渲染,无需重新生成节点。
被keep-alive包裹的组件会多两个钩子函数:activated和deactivated
有三个参数:
- include:要缓存的组件name,为数组 或者 字符串
- exclude:不需要缓存的组件name,为数组 或者 字符串。(优先级大于include)
- max:最大缓存的组件个数
(2)使用场景
1.动态组件可以采用keep-alive进行缓存
<keep-alive :include="array | string" :exclude="array | string" :max="count">
<component :is="component"></component>
</keep-alive>
2.在路由视图中使用keep-alive(也可以通过name定义命名视图)
<keep-alive :include="array | string" :exclude="array | string" :max="count">
<router-link></router-link>
</keep-alive>
3.也可以通过meta属性指定哪些页面需要缓存。需要在路由规则里配置参数
<div id="app">
<keep-alive>
// 需要缓存的组件
<router-view v-if="$route.meta.keepAlive" />
</keep-alive>
// 不需要缓存的组件
<router-view v-if="!$route.meta.keepAlive" />
</div>
36.Vue中使用了哪些设计模式?
- 单例模式:单例模式就是整个程序有且仅有一个实例,Vuex中的store
- 工厂模式:传入参数就可以创建实例
- 发布订阅模式:event bus,需要手动订阅和手动触发
- 观察者模式:自动通知,如数据变化,页面自动更新(如:watcher和&dep)
37.自定义指令的应用场景
指令的目的就是将操作DOM的逻辑进行复用
(1)指令的生命周期
- bind:只调用一次,指令第一次绑定到元素时候调用
- inserted: 被绑定元素插入父节点时调用
- update:组件的VNode更新时候调用,但可能发生在其子VNode更新之前
- componentUpdated:指令所在组件VNode以及其子VNode全部更新后调用
- unbind:只调用一次,指令与元素解绑的时候调用
(2)常见的指令编写
- 图片懒加载 v-lazy
- 防抖 v-debounce
- 按钮权限 v-has
- 自动聚焦 v-focus
- 加载中 v-loading
38.Vue的性能优化有哪些
- 数据层级不应过深,合理设置响应式数据:数据重写递归
- 通过Object.freeze方法冻结属性:冻结的不会重写get和set方法
- 使用数据时,缓存值的结果,不频繁取值
- 合理使用key属性
- v-show和v-if合理选择
- 控制颗粒度,Vue采用组件级更新,将复杂页面组件化,拆分组件
- 采用函数式组件,没生命周期和响应式数据,开销低
- 采用异步组件,将包进行拆分
- 使用keep-alive缓存组件 v-once
39.单页应用如何优化首屏加载速度较慢的问题
- 使用路由懒加载、异步组件,实现组件拆分,减少入口文件体积
- 抽离公共代码,采用splitChunks进行代码分割
- 组件采用按需加载方式
- 静态资源缓存,采用HTTP缓存,使用localStorage实现缓存资源
- 图片资源压缩,小图片采用base64方式内嵌到代码里,减少http请求
- 打包时开启gzip压缩处理,compression-webpack-plugin插件
- 静态资源采用CDN加速,终极手段
- 使用SSR(Service Side Render:服务端渲染)对首屏做好服务端渲染。后台直接返回渲染好的HTML
40.Vue项目中如何解决跨域问题
跨域是浏览器同源策略导致的,协议、主机名、端口不同都会导致跨域问题。
服务端和服务端之间进行通信是没有跨域问题的。
一般常用的解决方式有以下几种:
- CORS(Cross-Origin Resource Sharing,跨域资源共享)。由服务端设置,允许指定的客户端访问服务器
- 构建工具中设置反向代理,使用Nginx做反向代理
- 使用Websocket进行通信
- 搭建BFF(Backend For Frontend)层解决跨域问题
41.要做权限管理怎么做?如何控制到按钮级别?
(1)登录鉴权
用户登录后返回token,前端将token保存到本地,作为用户登录的凭证,每次发送请求的时候都要携带token。后端对token进行验证。当页面刷新时我们可以使用token来获得用户权限
(2)访问权限
根据用户是否登录来判断能不能访问某个页面,通过路由守卫来实现判断用户是否有此权限
(3)页面权限
前端配置的路由分为两部分“通用路由配置”和“需要权限的路由配置”。在权限路由中增加访问权限meta(备注)。用户登录后可得到对应的权限列表。通过权限列表筛查出对应符合的路由信息,最后通过addRoutes方法,动态添加路由
(4)按钮权限
按钮权限一般采用自定义指令来实现。当用户登录时,后端会返回用户的权限。在按钮上使用此指令,指令内部会盘点是否有此按钮权限,如果没有则会移除按钮
42.Vue-router有几种钩子函数,具体是什么,执行流程是怎么样的
- 导航被触发
- 在失活的组件里调用 beforeRouteLeave 守卫
- 调用全局的 beforeEach 守卫
- 在重用的组件里调用 beforeRouteUpdate 守卫
- 在路由配置里调用 beforeEnter
- 解析异步路由组件
- 在被激活的组件里调用 beforeRouteEnter
- 调用全局的 beforeResolve 守卫
- 导航被确认
- 调用全局的 afterEach 钩子
- 触发更新DOM
- 调用beforeRouteEnter 守卫中传给next的回调函数,创建好的组件实例会作为回调函数的参数传入
43.Vue-router几种模式的区别
(1)hash:url里有#
(2)history:historyAPI
44.本地项目开发完,部署到服务器上报404是什么原因
hash模式刷新不会有这个问题
history模式刷新时候会向服务端发起请求,服务端无法响应对于的资源,所以会出现404。这个时候就需要后端最一些配置,增加一个覆盖所有情况的候选资源:如果url匹配不到任何静态资源,那就返回同一个index.html页面。这就是app依赖的页面
45.谈谈你对Vuex的个人理解
(1)概念
- Vuex是一个专门为Vue.js应用程序开发的状态管理模式。采用集中式存储管理应用的所有组件的状态。核心就是解决数据的共享。
- 以相应的规则保证状态以一种可预测的方式发生变化
(2)状态修改
- 组件中同步修改: commit() => mutation => 修改状态
- 组件中异步修改: dispatch() => actions => commit() => mutation => 修改状态
(3)缺点
Vuex中store只有一份,对于复杂的数据,则需要依赖与模块。Vuex状态是一个树状结构,最终会将模块的状态挂载到跟模块上。
- 模块需要增加namespaced:true(独立的命名空间)
- 更改状态 mutation和action选取
- 模块和状态的名字冲突
(4)原理
Vuex3:核心就是创建一个new Vue实例,进行数据共享。
Vuex4:核心就是通过创建一个响应式对象,进行数据共享 reactive()
46.如何监听Vuex中数据的变化
- 通过Vue的watch监控Vuex中数据的变化
- 通过store的subscribe监控状态变化(多在插件里使用,平时业务基本用不到)
47.页面刷新后,Vuex的数据丢失怎么解决
- 每次获取数据前先检测Vuex里是否存在,不存在则重新调服务获取
- 把Vuex里的数据持久化处理,存储到localStorage里。获取的时候默认先从存储里取
48.mutation和action的区别
- action处理异步,action中可以提交mutation。mutation处理同步逻辑,修改数据
- 在action里可以多次进行commit操作,也可以调用其他的action
- 在非mutation里修改数据,严格模式下是会发生异常
- dispatch会将action包装成promise,mutation不会进行包装
49.有使用过Vuex的module吗?什么情况下会使用
使用单一状态树,应用所有的状态会集中到一个比较大对象,当应用比较复杂时,store对象就会变得比较臃肿,这个时候就最好使用模块。
Vuex允许我们将store分割成模块(module),每个模块都有自己的state、getter、mutation、action,甚至还会有自己的子模块
50.Vue3中CompositionAPI的优势是什么
- Vue2中采用的是选项式API(options api),用户提供data、methods、props、computed等。编写业务代码,复杂逻辑时会存在反复横跳的问题,来回上下找相关代码。组合式API可以将相关代码放到一起,方便维护。
- Vue2中所有的属性都是this访问的,this存在指向明确的问题
- Vue2采用mixins实现组件之间的逻辑共享,但是会有数据来源不明确、命名冲突等问题。Vue3采用CompositionAPI,提供公共逻辑也比较方便
- 简单的组件可以采用option API,复杂的组件组合式api优势更明显。
51.Vue3有了解过吗?跟Vue2有什么区别
52.Vue项目中的错误如何处理
(1)errorCapture钩子
可以捕获来自后代组件的错误。如果全局的config.errorHandler被定义,所有的错误仍会发送它。因此这些错误仍然会向单一的分析服务的地方进行汇报
组件错误会沿着组件层级一层一层的往上抛,如果errorCapture返回了false,则阻断传播。
(2)全局设置错误处理
Vue.config.errorHandler = (err, vm, info) => {
console.log( err, vm, info )
}
(3)接口异常处理
instance.inteceptors.response.use((res)=>{ ... },(err) => {
let res = err.respponse
if(res.status > 400){
handleError(res) // 统一处理错误信息
}
})
53.Vue3的内置组件Teleport
(1)概念
Teleport可以将一个组件内部的一部分模板“传送”到该组件的DOM结构外层的位置去。
例如定义一个模态框,嵌套的层级很深,维护样式的时候容易与外层的元素发生冲突。
(2)基本用法
Teleport接收一个to属性:指定传送的位置(也就是Teleport所包裹的组件渲染的位置)。to可以是CSS选择器、字符串、DOM元素对象。
渲染完成后,组件挂载的位置发生变化,但是父组件传入的props和触发的事件等逻辑仍旧正常,不会影响组件间的逻辑关系。
// main-body组件会挂载到#teId的元素内
<Teleport to="#teId" :disabled="false">
<main-body/>
</Teleport>
(3)禁用
Teleport绑定disabled属性,为true的时候,传送不会生效。
(4)多个Teleport共享目标
如果配置了多个Teleport,目标都一样的话,不会替换,而是会将内容叠加到一起。多个组件内容挂载在同一个DOM元素上,顺序就是简单的依次累加。
注意:Teleport挂载时,to目标必须已经存在于DOM中。
54.你知道那些Vue3新特性
-
组合式API: Composition API
-
SFC(single-file component:单文件组件) 组合式API语法糖:
-
Teleport内置组件:改变组件挂载位置,不会影响组件层级间的交互通信
-
Fragments:不再要求提供一个根节点,编译时会自动添加一个根节点Fragment
-
SFC内样式可以绑定动态数据
<script setup> const theme = { color: 'red' } </script> <style scoped> p{ color: v-bind('theme.color') } </style>
55.有封装过axios吗?主要封装哪方面?
- 设置请求超时时间
- 根据项目环境,设置请求路径(测试环境、生产环境)
- 设置请求拦截,添加token等
- 设置响应拦截,对响应的状态码进行判断,对数据进行格式化
- 添加请求队列,实现loading效果
- 登录超时,token失效,跳转登录页
- 维护取消请求token,在页面切换时通过导航守卫可以取消上个页面中正在发送的请求
56.isRef、unRef、toRef、toRefs、storeToRefs的用法
const obj = reactive({ age: 0 })
const count = ref(0)
(1)isRef
检查某个值是否为ref类型
isRef(obj) // false
isRef(count) // true
(2)unRef
如果参数是ref,则返回内部的值,如果不是,则返回参数本身
unref(count) // 0
unRef(obj) // Proxy(age:0)
(3)toRef
基于响应式对象上的一个属性,创建一个对于的ref。这样创建的ref与其源属性保持同步:改变源属性的值将会同步更新ref的值,反之亦然。
const age = toRef(obj, 'age')
isRef(age) //
const changeAge = () => {
// 以下两种方式更改数据后,obj.age 和 age都会同步改变
obj.age ++;
// age.value ++;
}
(4)toRefs
将一个非ref对象(普通对象或者reactive创建的对象)转成一个普通对象,对象里的每个属性都是具有响应式的ref。
使用的时候也可以直接将非ref对象里的属性解构出来,解构出的值具有响应式。
对象的每个属性都是指向源对象响应属性的ref,每个单独的ref都是使用toRef()创建的
const obj = ref({ age: 1 })
const obj1 = { a: 12 }
const nameObj = reactive({ name: 'herry' })
const { age } = toRefs(obj) // age: undefined
const { name } = toRefs(nameObj) // name: ObjectRefImpl {}
// objA还是普通对象,只是对象objA里的每个属性都有响应式
const objA = toRefs(obj1) // objA: {objA: ObjectRefImpl}
(5)storeToRefs
作用跟toRefs基本相同,都是作为解构对象使用,解构后的每个值都保持响应式。
区别是:
toRefs是vue的,处理的是组件内的响应式数据
storeToRefs是pinia的,处理的是pinia内的数据