跳到主要内容

谈谈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 操作的次数
  • 使用最长递增子序列优化了对比流程

    • 针对中间乱序部分,采用最长递增子序列的算法, 计算出乱序部分可以复用的最长连续节点
    • 用于找出新旧节点列表中的最长递增子序列,这些节点是不需要移动和重新创建的,只需要更新即可
    • 复用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索引都会变化