谈谈vue相关原理
谈谈vue3特性
- 响应式系统由defineProperty变更为proxy
- 支持Typescript
- 新增了composition API(组合API)
- 支持Tree-shaking
- 支持多个根节点组件 【Fragments】
- 静态提升
- 避免了重复节点渲染
- 事件监听缓存
- 将事件处理函数缓存起来,避免了组件渲染时重复创建函数的开销
- diff算法优化
- 最长递增子序列
- 减少移动dom的次数
- 静态标记
- 发生变化的地方添加一个
Flags标记,下次发生变化的时候直接进行比较, 不对比那些没有标记的
- 发生变化的地方添加一个
- 最长递增子序列
谈谈双向绑定
- 双向绑定 允许 视图层和数据层之间变化可以互相影响
- 在vue实例初始化时候,会遍历数据对象所有属性,vue2通过Object.defineProperty方法、vue3通过proxy 来转换为对应的getter和setter
- 当数据对象发生变化会触发setter,setter方法被调用,会通知对应视图更新
- 在vue中,v-model是实现数据双向绑定的语法糖
- v-moddel是input事件和v-bind的结合体
谈谈vue响应式原理
- 1、vue2中通过Object.defineProperty监听数据变化, vue3使用proxy, 当数据发生变化之后,会进行依赖收集,通过watcher会通知进行修改
- 每个数据都有自己的
dep属性,存放他所依赖的watcher
- 每个数据都有自己的
- 2、会进行diff算法来比较新旧虚拟节点数,得到最小的修改
- 3、然后调用watcher.updaer更新函数,更新改变的部分
谈谈vue3为什么使用proxy,废弃Object.defineProperty
- proxy可以直接代理监听整个对象,而Object.defineProperty只能代理对象的属性。
- Object.defineProperty需要递归遍历对象的属性,而且初始化使用的时候会进行监听收集依赖
- proxy是不需要深度遍历对象,直接就可以监听整个对象,而且在访问对象属性的时候才会触发依赖收集
谈谈虚拟DOM
虚拟dom本质是用JS对象模拟DOM树
- 减少了JS直接操作真实DOM的带来的性能消耗
- 减少DOM的操作
- 跨平台优势
- 虚拟DOM最大的优势是diff算法
- 提高性能
谈谈Vue的diff算法
- 双端diff算法
- 可以同时从头部和尾部进行比较,比较过程的效率更高,减少不必要的比较
Vue2 diff过程
-
采用了双端diff算法, 进行头比头,尾比尾,头比尾,尾比头
-
当进行组件更新时,Vue会进行新旧虚拟dom对比,只比较同一层级的, 不会跨层级比较
-
key和tag是否相同,如果相同,则认为是相同的节点(SameVnode)
- 如果不相同,直接删除,不做深度比较
-
对相同节点,会进一步比较,通过
patchVnode方法,会比较新旧节点的属性和子节点 -
如果新旧节点的子节点不同,Vue会使用
updateChildren函数来进一步比较子节点的差异,并进行相应的更新- 比较新旧节点的子节点,并进行更新。它会根据新旧节点的头尾指针进行比较,采用循环向中间靠拢的方式来找到差异,然后执行相应的更新操作
- 如果相同,则进行
patchVnode操作 - 如果不同,则进行
createElem创建一个新节点,并将其插入到正确的位置
Vue3 diff过程
vue3在vue2 diff的基础中进行了优化,优化如下
- 在
patchVnode过程中,Vue会比较新旧节点的静态标记【Patch Flag】,并进行相应的更新操作。如果新旧节点的静态标记和动态标记相同,则进行相应的更新操作;如果不同,则进行createElem操作或者移动节点的操作 - 在
updateChildren过程中,Vue会使用静态标记来比较新旧子节点的差异,并进行相应的更新操作 - 最长递增子序列优化了对比
Vue3 Diff算法和 Vue2的区别
Vue3 Diff算法做了如下优化:
vue3 diff核心是 减少移动dom的次数!
1、优化双端对比,头比头,尾比尾
2、使用最长递增子序列优化了对比流程
- 复用dom元素相同的情况,减少移动dom的次数
- 因为dom移动是很消耗性能的
3、静态标记PathFlags,vue会对发生变化的节点进行静态标记,只针对有静态标记的节点进行比对,而没有静态标记的,不用进行比对,直接复用
- 减少了比较的次数
编译阶段的优化:
-
事件缓存:将事件缓存(如: @click),可以理解为变成静态的了
-
静态提升:第一次创建静态节点时保存,后续直接复用
-
添加静态标记:给节点添加静态标记,以优化 Diff 过程
由于编译阶段的优化,除了能更快的生成虚拟 DOM 以外,还使得 Diff 时可以跳过"永远不会变化的节点",
Vue3 Diff 优化如下
-
Vue2 是全量 Diff,Vue3 是静态标记
- 通过静态标记,从而提升渲染性能
- 发生变化的地方添加一个
Flags标记,下次发生变化的时候直接进行比较, 不对比那些没有标记的
-
优化了双端对比,vue3只对比头比头,尾比尾
- 对比vue2 节省了头比尾,尾比头
- vue2双端对比:头比头,尾比尾,头比尾,尾比头
- vue3双端对比:头比头,尾比尾
- 对头尾两端同时开始对比节点,可以避免不必要的操作,并减少节点的更新次数
- 同时从新旧节点的头部和尾部开始比较,找到第一个不同的节点,然后进行更新。这种策略可以尽可能地复用旧的节点,减少 DOM 操作的次数
- 对比vue2 节省了头比尾,尾比头
-
使用最长递增子序列优化了对比流程
- 针对中间乱序部分,采用最长递增子序列的算法, 计算出乱序部分可以复用的最长连续节点
- 用于找出新旧节点列表中的最长递增子序列,这些节点是不需要移动和重新创建的,只需要更新即可
- 复用dom元素相同的情况,移动dom的次数减少
-
Vue 3通过利用Proxy和WeakMap的优势,在更新过程中更高效地找到需要更新的节点,从而提升了性能
- Vue 3使用Proxy来跟踪组件的属性访问,当访问到的属性发生变化时,会触发对应的更新操作
- 更新操作中,Vue 3利用WeakMap来缓存旧节点,通过旧节点在WeakMap中的映射,快速找到对应的新节点
WeakMap只保持对键的弱引用,不会阻止键被垃圾回收。这样,在组件不再使用时,旧节点和对应的缓存会自动被回收,不会造成内存泄漏。这对于长时间运行的应用程序来说尤其重要,因为它确保不会累积大量的无用缓存 【自动清理缓存中的数据, 避免缓存数据占用过多内存】
谈谈vue不使用react的分片更新的原因?
vue不需要时间切片原因:
- 时间切片只能对大量的CPU计算进行优化,无法优化复杂的DOM操作,因为要确保用户正在操作的界面是最新的状态才可以
- 而由于React里会经常出现CPU计算量大的场景 【100毫秒以上的计算量】
- React由于使用JSX导致它的渲染效率比 template 低,因为 template 很容易做静态分析和优化
- React的应用需要开发者手动优化,默认会有render渲染过多的问题,比如需要加useMemo优化渲染效率,指定正确的依赖数组,指定依赖这个在react中很难做到自动化
- 对比之下,vue解决了上述的问题,vue的template模板优化 【静态分析、静态提升】,而且vue通过数据响应式追踪,Vue 可以做到组件树级别的优化,比如把插槽编译为函数以避免 children 的变化引发 re-render,比如自动缓存内联事件处理函数以避免 re-render。Vue 3 可以做到在不借助开发者的任何手动优化的情况下,防止子组件在非必要的情况下 re-render。这意味着同样一次更新,React 应用可能要 re-render 多个组件,而 Vue 应用很可能只 re-render 一个组件
- 在默认情况下,Vue3 应用会比 React 应用少花费很多CPU 时间,所以vue不需要时间切片,也就不需要fiber
1、React是为了解决更新时候过多计算量引入时间切片
2、vue是响应式依赖追踪,可以做到细粒度的精确更新组件,而且vue的template模板优化,如静态分析、静态提升
谈谈vue的收集依赖
自动收集依赖
为什么react很难做自动收集依赖,而vue3却很容易做到
- React 并没有直接的方式来跟踪组件所依赖的状态或属性,因此在某些情况下,需要手动编写代码来处理依赖收集
- Vue 3 的响应式系统是基于 ES6 的 Proxy 实现的,这使得 Vue 能够更轻松地追踪数据的变化。当响应式对象被访问时,Vue 能够捕获到这个访问操作,并自动建立依赖关系。这种设计让 Vue 能够更容易地进行自动依赖收集
依赖收集过程
依赖收集目的:数据变化了可以更新
- 每个数据都有自己的
dep属性,存放他所依赖的watcher,组件在渲染过程中,会创建对应的watcher - 当数据发生变化会通知对应的
dep触发watcher, 调用Watcher的update方法来实现的
谈谈vue的nexTick
- 下次 DOM 更新之后执行回调
nextTick是vue中的更新策略,也是性能优化手段,基于JS事件循环机制实现
实现原理
nextTick的实现原理是利用事件循环来进行异步操作- 会在下一次事件循环之后,执行回调
- 由于兼容性问题,Vue 在内部对异步队列会进行优雅降级处理,尝试使用原生的
Promise.then、MutationObserver和setImmediate
使用场景
- 1、修改完数据之后,获取DOM元素
- 2、当需要获取拿到更新之后的数据,才执行下一步操作
谈谈vue的computed原理
读取computed就会触发get,设置的时候就会触发set
computed可以把计算结果缓存起来,只有当依赖数据发生变化时,才会重新计算- 只有当
computed属性值被访问使用的时候,才会执行,否则不执行
实现原理
-
通过
dirty属性来标记实现依赖缓存 -
当依赖数据更新时,
dirty属性会为true, 需要重新计算 -
ditry为false时, 读取computed的时候会使用缓存的数据 -
获取数据之后, 同时将
dirty设置为 false, 进行缓存
dirty表示脏数据的意思,如果脏了 (如果为true), 表示依赖数据更新了
谈谈vue初始化过程
- 创建vue的实例
- 初始化vue的指令、插件
- vue2中会进行合并options选项
- 调用生命周期钩子
谈谈vue模板编译
- vue会将模板转换为
ast抽象语法树 - 对
ast语法树进行优化、标记静态节点 - 代码生成
vue3模板编译优化
- 静态提升
- 避免大量静态节点的重复渲染执行
- 静态标记 PatchFlag
- 只有静态编辑的才会进行比较,才会进行追踪
- 事件缓存
- 将事件处理函数缓存起来,避免了组件渲染时重复创建函数的开销 【cacheHandles】
谈谈路由守卫作用
- 全局路由前置守卫可以使用router.beforeEach注册,会在路由跳转前触发。例如,可以在用户未登录时,跳转到登录页面‘、判断是否存在权限,否则跳转到登录页
- 全局后置钩子可以使用router.afterEach注册,会在每次路由跳转结束后触发。例如,可以在每次路由跳转结束后,记录用户的访问日志
谈谈Composition API和React Hooks区别
-
React Hooks存在限制主要有两条:
- 不能在循环、不要在条件或嵌套函数中调用 Hook
- 保证顺序
- hooks只能在顶层使用,不能在内部循环或条件判断中调用它
-
Composition API是基于Vue的响应式系统实现的
-
vue的
script setup仅执行一遍,而React 函数式组件 每次渲染都会执行hooks -
Composition API调用不需要考虑调用的顺序、可以在循环、条件、嵌套函数中使用
-
响应式系统自动实现了收集依赖,所以vue内部可以更好的做性能优化,而React Hooks需要手动传入依赖,而且还要保证依赖的顺序,如果依赖不正确,会导致执行多次hooks,或者导致死循环问题
-
谈谈vue的key
-
可以对数组节点的复用
-
在循环dom时,可以保证节点元素唯一
-
利于diff算法,为了提升渲染性能
-
diff算法会根据key来进行找到节点dom
为什么不建议使用index作为key
- 在数组节点移动或者删除的时候,会导致index索引发生变化,每个元素index索引都会变化