Vue原理面试题
4090字约14分钟
2024-07-09
一、mvvm理解
- 早期的mvc:在页面中进行操作,=> 后端路由 => 控制器 => 数据获取 => 控制器 => 回传给页面
- 大量的逻辑在控制器这一层
- 传统的mvc,大量的逻辑耦合在控制器层,所以维护非常困难
现在的mvvm
- 简化了这一层,即以数据驱动视图模型
- 传统的mvvm要求不能手动操作视图,
- Vue具备了mvvm的思想,但是Vue也有新增的ref属性用于操作视图
二、Vue2和Vue3的响应式数据
响应式数据的核心就是数据变化了我们能知道
- 对象在Vue2中使用defineproperty将数据定义为响应式数据(使用getter和setter属性),缺陷为需要递归所有的属性,不存在属性不能被监控到,对数组并没有使用defineproperty,因为用户不一定使用索引操作数组,且索引有可能变化
- Vue2中减少数据的层级
- 不需要双向绑定的数据就不要放在data中
- 合理使用Object.freeze将数组冻结,比如1000条死数据,先冻结,不然变化一下更新一下视图
- 尽量使用缓存数据
- 对象在Vue3中使用proxy将对象拦截,不用重写getter和setter方法,性能高,不需要递归每一项属性。
三、Vue中如何检测数组的变化
Vue2中没有使用defineproperty检测数组(性能差),Vue2中采用了重写数组的七个方法(pop、push、shift、unshift、reverse、splice、short) Vue3中使用proxy检测数组,不用递归,且性能更高
四、Vue中如何进行依赖收集
依赖收集的目的是等数据发生了变化,就会更新视图
- 每个属性都有一个dep属性,每个对象也有dep属性,每个组件在渲染过程中会调用new Watcher 生成一个渲染watcher(渲染watch、计算属性watcher、用户watcher)一个属性可能有多个watcher,反过来一个watcher也可能存在多个dep中
- 当取值的时候,如果Dep.target上有watcher就会将watcher收集起来,等会数据变化的时候会通知自己身上的dep,然后dep会通知所有的watcher执行update更新操作
五、如何理解Vue中的模板编译原理
模板编译的核心在于ast树 => render函数 => 生成代码
- 会将模板转换成ast语法树
- 对ast语法树进行优化,标记静态节点
- 代码生成,拼接render字符串 使用with + new Function()生成函数
优化:Vue会将一些节点都加以标记,然后生成vnode几点上会有所表现,vue3中的优化有pathFlag、blockTree、事件缓存、节点缓存
六、Vue生命周期是怎么实现的
生命周期钩子在内部会被Vue维护成数组(mergeOptions)和全局生命周期合并成为最终的数组,当到某个阶段的时候就调用callHook方法执行生命周期
七、Vue生命周期方法有哪些?一般在哪里发送请求
在哪儿发送请求都可以,主要看做什么,但是如果是同步操作那肯定要使用mounted 在服务器渲染的时候,我们无法使用浏览器的钩子 因为是异步的关系,ajax是要在同步完成之后才能拿到数据。 事件环!!!
八、Vue 组件的data为什么要是函数
比如定义一个组件,组件每次实例化的时候会将组件上的data合并,那么如果data是对象的话 在merge的时候,会将当前的data合并到组件实例内部,如果data是对象,data为引用数据类型,那么有可能会出现data数据被共享的问题,如果data为函数,那么data会执行一次,形成私有作用域,就不会出现污染的问题
九、Vue.mixin 的使用场景和原理
其价值在解决公共逻辑,比如生命周期、data等 缺陷:为来源不明确,命名可能出现冲突的问题 原理:mergeOptions,会将内容合并到全局Vue的Options上
十、Vue.nextTick在哪儿使用?原理是什么?
nextTick功能是批处理,多次调用默认会将逻辑暂存到队列中,稍后同步代码执行完毕后会采用,同步的方式为依次执行队列 开了一个异步定时器,同步执行代码 注: nextTick是将回调函数放到队列中,并不是开启一个异步任务,等宏任务执行完毕的时候会依次执行微任务队列 执行顺序具体看放入的顺序 ==> 渲染Dom ==> 执行回调 ==> 执行回调 ==> 渲染Dom this.a = 1 , this.a = 100
相关信息
会执行两次但是Dom只会渲染一次
十一、computed和watch的区别
区别是 computed:可以用于Dom渲染,computed只有在取值的时候才会执行对应回调,lazy为true所以不会立即执行,有一个dirty实现了缓存机制,多次取值如果依赖的值没有发生改变就不会更改dirty的结果 watch:不能用于Dom渲染,且watch默认会执行一次,watch可以实现异步的监听,变化时立即改变
十二、Vue.set是怎么实现的
为了实现给以前不存在的对象添加属性可以动态更新页面,对象只有在实例化的时候才会添加getter和setter属性,所以后添加的内容是检测不到的,
- Vue.set({},"name","val")
先判断对象是不是数组,而且是索引,那么就更改数组的长度,内部会调用splice方法 如果判断是对象,就手动赋值然后主动通知更新
十三、Vue为什么使用虚拟dom
- 最核心的内容是跨端 不同的平台实现方案不同,内部实现可以不局限于浏览器平台
- 如果开发者频繁操作dom,会出现浪费性能的情况,虚拟Dom增加了一层缓存,我们先更新虚拟Dom然后更新到页面上
- 多次操作dom浏览器会合并
十四、Vue中的DIff算法
Diff算法是O(n)级别的,采用的同级比较,内部是深度遍历方式,儿子和儿子比较 Vue2
- 先比较是否有相同的节点,key tag 相同的节点比较属性,属性相同复用老节点
- 比较儿子节点,考虑老节点和新节点的情况
- 头头、尾尾、头尾、尾头对比查找并进行复用
Vue3中使用最长递增子序列 二分查找+贪心算法+前驱节点
十五、Vue中key的作用和原理
可以的作用是标识唯一性,在diff算法的时候,可以进行复用。判断是否是相同的节点。
- key在动态列表中不要使用索引
如果使用的索引,相当于没有写key,而且可能会出现副作用,如果是不会改变的列表,就不会出现这种问题 在diff算法的时候,vue会对比虚拟节点,如果type和属性一样,再对比key是一样的,那么就认为他是可复用的,如果key为index,index是会发生变化的,所以就会发生复用错误的问题
十六、谈一谈对组件化的理解
组件最早出现在webcomponent浏览器可以实现自定义标签,但是很大的问题是性能很差。 Vue中组件化的好处
- 是实现组件级别的更新,合理该规划代码,可复用性很强,单向数据流
- 可以在组件中使用插槽、事件便于二次开发
十七、Vue的组件渲染流程
编写组件(用的时候都是使用标签) 组件 => ast语法树 => 创建虚拟节点 => 挂载到父组件的对应位置上
- 注册组件,在当前实例中获取到组件
- Vue.extend根据组件对象创造一个组件类,包含生命周期钩子,init方法还包含 (Sub,children...)
- 然后进入渲染阶段,当组件首次渲染的时候会调用组件的init方法,
- 根据组件的内容生成虚拟节点,创建节点,插入到页面上
生命周期钩子会先调用父组件的beforeCreate,created,beforeMount然后执行子组件的beforeCreate,created,breforeMount,mounted最后执行父组件的mounted 如果发生更新的情况 父组件beforeUpdate => 子组件 beforeUpdate =>子组件updated => 父组件updated
十八、Vue组件更新流程
什么情况会导致组件更新
- 自己的状态发生了改变
- 父组件传过来的props变化,如果内容在组件中被使用了
- 父组件更新更新,导致子组件也发生了更新
十九、Vue中异步组件渲染原理
对标图片的懒加载,流程是先渲染一个空节点,之后组件加载完毕了,需要重新强制渲染,重新进行组件加载
二十、函数组件的优势及其原理
缺点是无状态,无生命周期,没有自己的数据源,可以接收props,单纯的页面渲染可以采用函数组件,正常组件是一个类_init(),但是函数组件是一个函数,性能相对来说比较高 单纯的只用来展示,不用来交互,他没有watcher所以做不了响应式,如果父级被重新渲染,那么函数式组件就重新渲染
二一、Vue组件的传值方式以及之间的区别
- props,emit 基于事件订阅
- eventBus 在全局上的事件订阅
- Vuex 全局状态管理
- inject provide 开发组件库可以使用
$parent
$children
获取父子的实例- refs 父拿到子的实例
- attrs
$listeners
父组件传递给子组件的所有属性和方法
二二、props和emit实现
props 在创建虚拟节点的时候,会被抽离到components中的propsData中,就是在初始化的时候将propsData定义在组件的_data上,最后代理到实例上 emit 给组件绑定事件,核心就是基于发布订阅,$on
和 $emit
,解析时会将队列中的对应事件拿出来执行
二三、$parent
和 $children
在组件初始化的时候,会构造父子关系,然后直接访问就好了
二三、provide 和 inject
父组件将数组定义在vm._provide上 子组件通过vm.$parent
向上找,最后找到属性然后定义在自己的实例上 provide 和 inject不是响应式的
二四、$attrs
和 $listeners
获取所有的属性和事件直接定义在实例上,可以将事件和属性快速的一层一层传递,不能实现跨级传递
二五、$refs
在实例上定义$refs
在实例化的时候映射虚拟节点
二六、attrs是为了解决什么问题的,provide和inject不能解决他的问题吗?
可以快速的将属性向下传递,一层一层传递,不能实现跨级别传递 provide 和 inject主要是跨级通信,不用再进行传递了,可以在父组件提供出来,子组件直接使用
二七、v-if 和 v-for哪个优先级更高
vue2 v-for 比 v-if优先级更高一些 vue3 v-if 比 v-for 优先级更高 如果再写一次就会每一次循环都会做次判断,先创建,然后再删除,非常浪费性能 我们可以在外边包一个template先判断渲染,然后再循环 v-for的原理就是拼接一个循环函数,内部的方法是 -l v-if自动会被定义成三元表达式,
二八、v-if、v-for、v-model的原理
v-for原理就是拼接一个循环函数,内部用了一个方法 _l v-if 自动会被转义成三元表达式 (v-for和v-if)并不会编译出directive来,再生成代码的时候就将这两个东西转义 v-model原理实际上就是双向绑定原理 v-model在组件中就是value 和 input的语法糖,如果不想用value和input这个名字,可以使用 model:{prop:"xxxx",event:"xxxx"}
实现对value和input默认的修改 如果是放过在表单上就会有差异,如果放在表单上会被拼接成指令,比如输入中文他不会每一个都渲染,而是默认给input事件拼接一个处理中文的指令,在中文输入完毕时渲染。
二九、slot是怎么实现的,什么时候使用他
插槽分为三种
- 具名插槽 构建一个映射表
- 普通插槽 是在父组件中渲染,他只能用父组件的数,渲染后传递给子组件,封装普通组件
- 作用域插槽 是在子组件中渲染的(vNode),可以使用,子组件数据来继续渲染,常用的为表格中自定义列的内容
三十、Vue.use是干什么的?原理是什么
- 使用VUE插件,都会使用Vue.use( plugin ) 主要用于保证vue版本,分离插件和vue的强依赖
- 原理是将传入的插件放在传入的vue的原型上,用的是传入的vue,所以不会产生vue版本的问题
三一、组件中name选项的好处和作用是什么
- 好处1. 可以在自己的组件中,循环自己的组件
- 好处2. 有了名字后,可以具体定位到具体组件,不停的向上查找找到某个组件,给这个组件派发状态
三二、vue修饰符有哪些,原理是什么
stop、prevent、capture、self、once、passive 有两种使用场景
- 组件在编译的时候,会对一些修饰符做处理,根据不同的修饰符,生成不同的代码,
- 组件在运行时,执行相应的处理
- once、passive、capture得再绑定事件的时候进行特殊处理,在创建的时候依次调用对应的属性钩子来实现创建对应的功能
三三、vue中.sync的作用和实现原理
.sync是解决v-model不能绑定多个的问题,写法为 <My :xxx.aync="xxx" :yyy.sync="yyy">
当需要触发事件的时候,需要使用 $emit("update:xxx", value)
进行触发事件和双向绑定 在vue3中.sync被删除了 vue3中是可以写多个v-model的,写法为 <My v-model.xxx="xxx" v-model.yyy="yyy">
当需要触发事件的时候,需要使用$emit("update:XXX", value)
进行触发事件和双向绑定 所以.sync就函数掉了
三四、如何理解自定义指令
- 在生成ast语法树的时候,遇到指令会给当前元素添加directives属性
- 通过genDeirective生成代码
- 在patch前将指令的钩子提取到cbs中,在patch过程中调用对应的钩子
- 当执行cbs对应的钩子时,调用对应的指令定义方法
三五、keep-alive平时在哪里使用,原理是什么
- 用于缓存组件的实例,组件的实例上
vm.$el
缓存实例就是缓存了dom元素,组件切换的时候如果有缓存,直接复用上次vm.$el
的结果 - keep-alive不用做任何渲染操作,内部使用了LRU算法来管理缓存,最近最久未使用法,当超过最大限制的时候,删除最久没有使用的,如果最远的被使用了,就将其优先级提高到最近
- 实际上keep-alive是抽象组件,通过props传递 include exclude max最大缓存个数 怎么缓存? 将插槽的实例以key和value的形式存放在对象中,key也放在数组中,当执行的时候,判断有没有缓存,有缓存就使用,判断需不需要缓存,需要缓存就缓存,不需要就移除,再看看超没超过最大存放数。 缓存怎么更新,当插槽更新的时候,会调用强制更新keep-alive