Vue
1.谈谈你对Vue的理解
- MVVM:VM层相当于MVC分层当中C层的一部分特定职能的分离,即实时监控数据和读取数据变化,当数据修改时,及时通知视图更新。
- 响应式:与VM本质相同,提法不同,都是数据驱动视图,声明响应式数据,当数据变化时,驱动视图更新。
- 组件化开发:把一个页面分隔成若干个小块(组件),每个组件都有自己的模板,数据,样式,逻辑,相互独立,互不干扰,目的就是为了方便复用。
- 虚拟DOM:把真实的DOM结构映射成JS对象,目的就是为了,当数据发生变化时,能够快速的递归比较出发生变化的节点,比较的过程就是diff算法的过程。
- diff算法:对数据实行差量渲染,最大化的使用上一次缓存的结果,前提是需要得到两个新老虚拟DOM的不同之处。在暴力递归的基础做了很多优化,例如用key去唯一标识特定的节点。
- SPA单页面应用开发框架:当浏览器的路由地址发生变化时,是在同一个页面里面切换不同的组件,实现了整站开发的效果。
2.列表当中key的作用
- 在diff算法的过程当中,提升了很大的性能
- 当新老虚拟DOM的key相同时,直接认定其节点的整个DOM结构没有发生变化,直接使用上一次老的虚拟DOM渲染的结果,不带对其进行深入的递归比较,极大的提高了性能。
- 当新老虚拟DOM的key不同时,也不带对其节点进行深入的递归比较,而是直接把老的干掉,原地根据新的数据进行渲染新的节点。
3.computed和watch的区别
- 共同点:都是侦听一手数据状态变化,从而触发相应的逻辑
- computed:专注于根据一手数据状态变化换算出二手数据状态,即计算属性
- computed只有在计算属性依赖的数据项发生变化时,才会重新根据新的一手数据状态进行计算,否则直接复用上一次的渲染结果。
- computed不应该产生副作用。
- watch是专门侦听特定数据的变化,从而触发相应的副作用逻辑。
4.Vue的生命周期有哪些
- 创建实例阶段(init option):beforeCreate,created
- 挂载阶段(渲染DOM):beforeMount,mounted
- 数据更新阶段(数据驱动视图):beforeUpdate,updated
- 卸载阶段(渲染条件不成立/用户切换页面):beforeUnmount,unmounted
- Vue2当中两个卸载阶段:beforeDestroy,destroyed
- 八大生命周期图解:
5.父子组件生命周期联动
- 原则:父组件搭台,子组件唱戏
- 创建实例与挂载阶段:父组件创建实例完毕并预备挂载,子组件一一创建实例完毕并挂载完毕,父组件宣布整体挂载完毕。
- 数据更新阶段:父组件预备更新,子组件一一更新完毕,父组件宣布整体更新完毕。
- 卸载阶段:父组件预备卸载,子组件一一卸载完毕,父组件宣布整体卸载完毕。
6.Vue有哪些常用的修饰符
- 事件修饰符:stop、prevent、capture、once、self
- 键盘修饰符:esc,ctrl,shift,meta,enter
- 系统修饰符:exact
- v-model修饰符:lazy,number,trim
- 自定义v-model修饰符
- 自定义指令修饰符
7.v-if和v-show的区别
- 渲染条件不成立时:v-if压根就不存在,v-show不显示(display:none)
- 适合使用v-show的场景:频繁的切换显隐
- 适合使用v-if的场景:一锤子买卖(例如某某电影专区,需要VIP才可以观看)
8.谈谈你对nexttick的理解
- 当数据变化时,无法立即拿到最新状态下的DOM
- 数据变化是立刻完成,渲染跟进是需要时间的(异步)
- 可以通过nexttick(()=>{在这里可以拿到最新的DOM状态})
- 在nexttick回调函数当中,是在DOM结构渲染(更新)完毕才回调,这时候就可以拿到DOM的最新状态
9.什么是具名插槽以及什么是作用域插槽?
- 具名插槽:就是插槽内容有很多,需要指定某些内容到哪个插槽当中,就需要指名道姓
//子组件
<son>
<slot name="head">
<P>我是head具名插槽的默认内容</p>
</slot>
</son>
//父组件
<father>
<template #head>
<P>我是外界注入的head插槽的内容</p>
</template>
</father>
- 作用域插槽:父组件在自己的模板上部署子组件及其插槽内容时,只能使用父组件自身作用域内的数据,要想子组件内部的数据,就需要用到作用域插槽
- 作用域插槽,子组件需要自己暴露数据供父组件使用,暴露的数据,会以一个对象的方式,父组件只需起一个别名接收即可
//子组件
<son>
<slot name="head" :age="18" :color="red">
<P>我是head具名插槽的默认内容</p>
</slot>
</son>
//父组件
<father>
<template #head="newObj">
<p>{{newObj.age}}</p>
<p>{{newObj.color}}</p>
<P>我是外界注入的head插槽的内容</p>
</template>
</father>
10.Vue组件通信方式有哪些?
- 父子通信:prop down
- 子父通信:event up
- 祖孙通信:provide + inject
provide() 接受两个参数:第一个参数是要注入的 key,可以是一个字符串或者一个 symbol,第二个参数是要注入的值。
<script setup>
import { ref, provide } from 'vue'
// 提供静态值
provide('foo', 'bar')
// 提供响应式的值
const count = ref(0) provide('count', count)
</script>
<script setup>
import { inject } from 'vue'
// 注入值的默认方式
const foo = inject('foo')
// 注入响应式的值
const count = inject('count')
// 注入一个值,若为空则使用提供的默认值
const bar = inject('foo', 'default value')
// 注入一个值,若为空则使用提供的工厂函数
const baz = inject('foo', () => new Map())
// 注入时为了表明提供的默认值是个函数,需要传入第三个参数
const fn = inject('function', () => {}, false)
</script>
- 子组件自己暴露给父组件:expose
//子组件
export default {
expose:{
myname,
myage,
sayHelloFn
}
}
//父组件
<son ref="sonRef"/>
this.$refs.sonRef.sayHelloFn()
//Vue3
//子组件
<script setup>
import { ref,defineExpose } from 'vue'
const a = 1
const b = ref(2)
defineExpose({
a,
b
})
</script>
//父组件
import { ref } from 'vue'
<son ref="sonRef"/>
const sonRef = ref(null)
sonRef.value.a
- 使用全局状态管理自由通信:把需要共享的数据状态都存储在【具有响应式的中央数据仓库里】使用vuex/pinia,并且对外暴露CRUD接口,任意组件去修改数据状态,其他用到改状态的组件,都会具有响应,根本不必去可以通信
Vue性能优化
缓存系列:最大程度使用缓存
- computed
- 动态组件与keepalive
- 本地缓存网络数据
- 服客双方配合使用HTTP的强缓和协缓
偷懒系列
- 使用异步路由:需要用到组件时,再进行加载对应组件
- 防抖与节流(减少时间处理)
- 表单v-model.lazy="xxx"(本质上是不理会Input事件,只理会Change事件)
- 避免无效回流(不要对一个DOM元素连续做多次样式操作,集合起来做一次)
- 合理使用v-if和v-show
减少渲染和diff压力
- DOM结构尽量扁平化(幽灵标签)
- 数据结构尽量扁平化(可能涉及到深度监听)
- 避免不必要的深度监听
- 对大列表渲染时加key(diff算法时减少很多深度递归比较)
节约内存
- 避免内存泄漏,组件卸载时,需要把自创的定时器、事件监听器、闭包、全局变量统统释放
对v-model双向数据绑定的理解
- 在一对多通信的过程中,我们更加推崇单向数据流,简单清晰,好管理
- 在一对一的通信过程中,我们使用单向数据流,不会存在复杂逻辑和难管理的问题,合理使用能给我们带来极大的便利
- 表单上的v-model本质上是
父子通信prop+子父通信 emit的集成,是一种语法糖 - 组件上使用v-model,通过
prop modelValue+@update:modelValue来实现 - 组件上v-model使用自定义参数,通过
prop xxx+@update:xxx来实现
对props单向数据流的理解
- 在父子通信,数据只能由父组件传递给子组件,子组件不应该去修改父组件的数据(可以修改,但是会报警告),当有多个子组件同时去修改父组件上的数据时,会造成结构上的混乱
- 如果子组件真的要修改父组件数据,可以通过发送自定义事件给父组件,由父组件自行修改,子组件也能获得同步响应
- 在一对多通信当中,我们推崇单向数据流,简单清晰,好管理
- 对特定数据修改入口唯一化,是科学的编程理念,政出多门,编程大忌,治乱之由也
Vue有哪些逻辑复用方式
- Vue2主要是
Mixin - Vue3主要是
自定义hook Mixin就是一个渣渣,全局,局部到处都可以定义,相互冲突和覆盖,发生错误时,难以追踪和调试,因此并不好用自定义hook,很好很强大,Vue3升级的初衷之一(或者没有之一)- 另外还有
自定义指令+自定义插件
为什么data选项是一个函数
- 为了形成封闭的作用域/闭包
- 每个组件之间的数据相互独立,互不冲突,都不污染全局
- ps:谈谈你对闭包的理解? juejin.cn/post/717031…
谈谈对Vuex的理解
- 实现数据和视图分离
- 实现组件之间的数据共享和自由通信
- 实现全局缓存数据(缓存在localStorage),需要配合persistedState数据持久化插件来使用
- 单向数据流架构:组件派发action编辑数据,action异步通信获取数据,action提交mutation并令其同步修改数据,mutation同步修改state(devtool会知道),订阅state的组件也会得到响应
- 在一(这样数据仓库)对多(组件)通信中,单向数据流简单清晰好管理
- Vuex单向数据流图解:
Vue怎么缓存当前组件?缓存后想更新怎么办?
- 使用keepalive动态缓存组件,被切换掉的组件实例不会被卸载
- 可以keepalive身上配置include(正选),exclude(反选)
- 还可以在keppalive身上配置Max最大缓存实例数,底层采用的是(LRU缓存算法),当缓存的组件实例数超出时,最近最少使用的组件实例将会被卸载掉
- 当组件被切出时,会回调deactived(失活)生命周期,被切入的组件,会回调actived(激活)生命周期,可用于缓存和更新数据
有没有封装过axios
- 经典三层实例封装模型:实例层+CRUD层+应用层 视图层只与应用层打交道
- 实例层:对axios实例的封装,包括一些(baseUrl,timeout)的基础配置,拦截器
- 拦截器分为请求拦截器+响应拦截器
- 请求拦截器可以注入一些通用的配置,例如登陆时Token,headers:{authoriation:Bearer TokValue}
- 响应拦截器可用于过滤数据,例如返回 response.data
- CRUD层封装了通用的POST/DELETE/PUT/GET请求,应用层只需要注入url,data,config即可;
- CRUD层还可以用来统一处理错误
- 应用层调用CRUD层的增删改查方法,并对视图层暴露出业务API
Vue3有哪些更新?
底层响应式原理的更新
- 使用Proxy代替了Vue2的数据劫持侦听Object.defineProperty
- Proxy机制:数据对象的每一个叶子节点背后都是一个代理,访问数据一律通过其代理,所以当数据更新时,代理会通知相应的视图进行更新,无需递归比较整棵数据树,极大的提高了性能
- 数据劫持侦听的缺点:
- 1.性能垃圾:需要递归比较出新老数据的所有叶子节点
- 2.只能监听get与set操作,当数据对象的地址不发生变化(例如:删除对象里面的key,原地修改数组),是无法监听到的,(做了很多复杂的周边处理才填上这个坑)
- 底层响应式原理的更新并不直接体现在编码上
逻辑复用机制更新
- Vue2的跨组件实现逻辑复用方式是
mixin mixin,就是一个渣渣,在全局局部,到处都可以定义,相互冲突和覆盖,发生错误时,难以追踪和调试,因此并不好用- Vue3采用组合式API,可以把响应式数据定义逻辑,生命周期的回调逻辑,数据侦听逻辑集成到一个函数当中,能够轻松的实现在各组件之间实现业务逻辑共享,并且不会产生副作用
- 响应式数据的定义逻辑:ref定义单个地址,reactive定义一整个响应式对象,toRefs把整个响应式对象打散成一群ref组成的普通对象
- 生命周期的回调逻辑:setup本身就已经充当的创建阶段(beforeCreate,created),剩下的六大生命周期以普通回调函数的形式存在,分别为onBeforeMount,onMounted,onBeforeUpdate,onUpdated,onBeforeUnmount,onUnmounted
- Vue2当中的最后两个生命周期分别为:beforeDestroy,destroyed
- 还有keepalive缓存动态组件实例当中两个失活(deactived)与激活(actived)生命周期,分别用于缓存和更新数据,Vue3当中分别对应为onDeactived,onActived
- 独立的数据侦听逻辑:
const xxx = computed(()=>基于一手数据状态创建的二手数据)
用于创建独立存在的计算属性
const unwatch = watch(()=>{
//监听数据项
()=>currentPage,
//副作用逻辑
(nv,cv)=>{
console.log("currentPage changed",nv,ov);
emit("pageChange",nv)
},
//config项
{
deep:true, //是否进行深度监听
immediate:true //是否一开始就立刻回调
}
}
watchEffect():自动收集所有依赖项
都是用于创建独立的数据变化侦听和副作用逻辑
- 组件只做展示和交互,数据交给页面级组件管理
什么是自定义hook
- Vue3当中跨组件复用的逻辑方式
- 自定义hook是把响应式数据的定义逻辑,生命周期的回调逻辑,数据侦听逻辑,集成到一个函数当中,并且返回一个具有响应式的对象,轻松的实现在组件之间共享业务逻辑,不会产生副作用
- 典型的手写案例:useCountDown,useScroll,useMouse