vue面试题

187 阅读12分钟
  • reactivity:响应式系统
  • runtime-core:与平台无关的运行时核心 (可以创建针对特定平台的运行时 - 自定义渲染器)
  • runtime-dom: 针对浏览器的运行时。包括DOM API,属性,事件处理等
  • runtime-test:用于测试
  • server-renderer:用于服务器端渲染
  • compiler-core:与平台无关的编译器核心
  • compiler-dom: 针对浏览器的编译模块
  • compiler-ssr: 针对服务端渲染的编译模块
  • compiler-sfc: 针对单文件解析
  • size-check:用来测试代码体积
  • template-explorer:用于调试编译器输出的开发工具
  • shared:多个包之间共享的内容
  • vue:完整版本,包括运行时和编译器

image.png

image.png

image.png

Vue3对比Vue2的变化

  • 性能优化(更快)
    • 使用了proxy代替了object.defineproperty(为什么?需要递归给对象中的所有属性增添getter和setter,重写性能差,对新增的属性和删除的属性也无法监控vm.setvm.set vm.delete。对数组也能监控但是性能依然偏差。defineproperty不支持新的数据结构map和set都不支持)
    • 模板编译优化:编译的过程中给节点增加patchflag标记;对静态节点静态提升,函数缓存
    • diff算法(全量diff最长递增子序列,可以减少服用节点的移动)(非全量diff算法,只比较动态节点。通过patchflag更新动态的属性,减少递归操作)
  • 体积优化(更小)
    • Vue3移除了很多特性
      • Vue2中的inline-template基本用不到所以干掉了
      • 删除了过滤器(通过计算属性来看替代、或者自己写个方法来替代)
      • new Vue()=》eventBus onon off $emit(发布订阅)官方觉得不需要了如果有需要可以通过mitt库来实现
      • .native .sync(.native 不需要Vue3中默认就是native,.sync没有了可以直接通过v-model)@keyup.enter(无法通过keycode来实现修饰符)
      • $children来封装组件交互(Vue3中不再有此方法provide和inject)
      • 全局api都不再使用了 Vue.use vue.component vue.directive
    • vue3所有的api都是基于函数可以按需导入,配合构建工具可以实现tree-shaking,用户最终打包的代码体积小。
  • createRenderer自定义渲染允许跨平台Vue2中只能改源码,扩展能力更强。
  • Vue3支持TS源码采用monorepo分模块打包,可以不引用完整的Vue

如何看待composition api和options api?

  • 如果采用options api 会将代码逻辑分散到不同的地方,会出现反复横跳的问题,可以将相关的逻辑放到一起
  • 再Vue2中想让逻辑服用Vue.mixin(命名冲突,数据来源不明确),组合式可以方便的提取逻辑方便复用。
  • this问题,导向不明确,无法支持tree-shaking。
核心就是将业务逻辑相关的代码放在一起,可以复用。

如何理解reactive、ref、toRef和toRefs?

  • reactive:特点将普通对象转化为响应式对象(采用的是new proxy),我们渲染的时候会使用产生的代理对象,当我访问代理对象的属性的时候会进行依赖手机,当修改属性时会触发更新。
  • ref:创建一个包装对象,将基本数据类型的值进行包装成响应式对象。通过这个对象的.value来进行访问值,访问的时候会做依赖收集,修改.value值会触发更新(采用的是类的属性访问器),ref内部如果放的是对象也会采用reactive来实现
  • toRef:基于reactive将某个reactive中的某个属性转换成ref
  • torefs:基于reactive将所有的属性都转换成ref。

vue2,vue3 diff算法的区别

  • 对于Vue2和Vue3的全量diff算法 整体来说性能差异不大
  • 默认都是采用同级比较+深度递归的方式进行比较
  • Vue3中拥有最长递归子序列的算法优化了移动复用节点的频率
    • diff算法的原理:1)先比较根节点isSameVnode
    • 如果不是相同节点删除后创建新节点,如果是相同节点则复用比较属性和儿子
    • 一方有儿子 一方没儿子 两方都没儿子 两方都有儿子
    • 两方都有儿子才是diff算法的核心(先比头在比尾,确定是否是特殊情况,是的话直接操作,如果不是则采用最长递归子序列来进行优化复用节点)
  • Vue3中对比Vue2性能优化在编译原理 编译过程中会添加patchflag,会产生对应的block节点来进行动态节点的收集,更新的时候只比较动态节点即可(靶向更新,减少了递归比较的方式)

v-if和v-for的优先级哪个高

image.png

Vue中的v-show和v-if怎么理解?

v-show控制的是样式,控制的是display 不管条件成立不成立都会执行 v-if 控制的是dom的显示隐藏(v-if具备阻断内部代码的执行,如果条件不成立不执行内部逻辑,如果成立则执行) 在页面第一次加载的时候可以确定是显示还是隐藏则用v-if如果是频繁切换显示隐藏则用v-show

image.png Vue3中如何进行组件通信 image.png image.png

谈谈pinia

  • Vuex只有一份,基于复杂的树结构,管理困难
  • 模块和根状态名字冲突怎么办?
  • 数据调用的时候,数据非扁平化
  • mutition和action的区别
  • 模块避免公用需要增加namespaced
  • 不是用ts写对ts支持也不好
  • 基于optionsapi针对Vue3不友好

Vue-router

  • Vue-router两种前端模式hash(丑,不会出现404,但无法seo优化)history模式(好看,需要服务端支持来解决404问题,可以seo)
  • 404问题咋解决,后端访问不存在的资源跳转到首页,首页会加载JS根据路径渲染对应的组件
  • 路由守卫的执行过程(组合函数,promise链)
  • 菜单权限addroute,访问权限Meta属性来做限制(路由守卫)

Vue中异步组件的作用及原理

defineAsyncComponent来定义异步组件,内部传递工厂函数,来异步加载组件

  • 类似图片懒加载,默认展示异步占位符,后续加载完毕后显示真正内容(响应式原理)
  • 一般配合工程化工具实现代码分割

Keep-alive 的原理

  • 缓存组件的虚拟节点 虚拟节点中有真实Dom 组件不是真正被销毁而是和真实节点做个映射后续激活的时候根据虚拟节点找到真实Dom 直接将真实Dom渲染无需重新创建,也就是直接服用老街店
  • 缓存算法LRU最近最久使用法
  • keep-alive因为走了缓存而数据无法更新(beforeRouteUpdate,activated来更新数据)

vue2与vue3中diff算法的区别

Vue 2 和 Vue 3 在虚拟 DOM 的 diff 算法上有一些区别。以下是它们之间的主要区别:

虚拟DOM算法:Vue中通过比较新旧虚拟DOM树之间的差异来最小化更新操作,从而提高渲染效率。虚拟DOM算法主要包括两个步骤:Diff算法和更新操作。

原理:Vue将模板编译成虚拟DOM树,并将其与上一个虚拟DOM树进行比较,找出需要更新的节点并进行更新操作。

1,Vue 2 使用的是基于递归的双指针的 diff 算法,而 Vue 3 使用的是基于数组的动态规划的 diff 算法。Vue 3 的算法效率更高,因为它使用了一些优化技巧,例如按需更新、静态标记等。,

2,Vue 2 的 diff 算法会对整个组件树进行完整的遍历和比较,而 Vue 3 的 diff 算法会跳过静态子树的比较,只对动态节点进行更新。这减少了不必要的比较操作,提高了性能。

Vue 2 的 diff 算法对于列表渲染(v-for)时的元素重新排序会比较低效,需要通过给每个元素设置唯一的 key 来提高性能。而 Vue 3 的 diff 算法在列表渲染时,通过跟踪元素的移动,可以更好地处理元素的重新排序,无需设置 key。

Vue 3 的 diff 算法对于静态节点的处理更加高效,静态节点只会在首次渲染时被处理,后续更新时会直接跳过比较和更新操作,减少了不必要的计算。

1.vue2和vue3中响应式处理数据的不同

vue2中采用的是Object.defineProperty和原生的gettersetter方法,当我们初始化一个数据data的时候,会实例化一个Observe类,首先它会迭代遍历data中的每一个属性,并通过Object.defineProperty给遍历到的每一个属性添加上gettersetter方法,当属性被读取的时候就会触发getter方法进行依赖(Wather)收集,当属性被修改的时候就会触发setter方法进行依赖(Wather)触发。 (但是,光靠这些方法并不能建立完全的响应式数据与依赖之间的关系,这种办法效率低、功能弱。当我们使用Object.defineProperty()来进行数据劫持,只有当数据被修改或者读取操作的时候才会触发组件的渲染,当涉及到组件中数据的增加删除操作时就不能触发组件更新渲染,还需要我们手动在数组的增删方法内通过重写的方式,在拦截里面进行手动收集依赖触发依赖进行视图更新)

// 实现observe函数,对data对象中的所有属性进行数据劫持
function observe(data) {
  if (!data || typeof data !== 'object') {
    return
  }
  Object.keys(data).forEach(key => {
    defineReactive(data, key, data[key])
  })
}

// 定义defineReactive函数,通过Object.defineProperty()函数来监听数据变化
function defineReactive(obj, key, val) {
  observe(val)
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get: function reactiveGetter() {
      console.log('get value:', val)
      return val
    },
    set: function reactiveSetter(newVal) {
      console.log('set value:', newVal)
      val = newVal
    }
  })
}

// 测试代码
const obj = { name: 'zj', age: 20 }
observe(obj)
obj.age = 22
console.log(obj.age) // 输出:22

vue3中不同的是采用了ES6新增的Proxy进行代理操作,他提供了一个创建代理对象的构造函数,与vue2不同点就在于它会对整个对象进行监听和拦截,省去了每次迭代遍历data属性的性能开销,并且里面有内置的gettersetter方法,在被读取、赋值、删除属性等操作的时候触发getter方法进行依赖收集,在修改属性值的时候触发setter方法,对收集到的依赖进行触发。vue3里面我们一般通过两种方式来响应式处理一个数据,分别是refreactive,简单来讲,ref可以响应式处理原始数据类型和引用数据类型,但是reactive只可以处理对象,原因就在于Proxy只能接受一个对象作为参数。 具体看这篇博客juejin.cn/post/723988…

2. Vue的模板编译原理是什么?请说明它的优化策略和实现方式。并手动实现一个简单的模板编译器。

模板编译原理:Vue中将用户写好的模板转换成渲染函数,实际渲染时调用该函数进行渲染。模板编译主要包括三个阶段:解析、优化和生成。

优化策略:Vue在模板编译阶段会对模板进行静态节点标记和静态根节点标记,从而可以避免不必要的重复渲染和提升整体渲染性能。

实现方式:Vue通过将模板解析成抽象语法树(AST),再转换成render函数的方式来实现模板编译。最终生成的render函数就是一个虚拟DOM的描述对象,然后通过虚拟DOM的diff算法进行渲染更新。

function compile(template) {
  var element = document.createElement('div');
  element.innerHTML = template;

  Array.from(element.childNodes).forEach(function(node) {
    if (node.nodeType === Node.TEXT_NODE) {
      var reg = /\{\{(.+)\}\}/;
      var match = node.textContent.match(reg);
      if (match) {
        var key = match[1].trim();
        node.textContent = '{{' + key + '}}';
        node._key = key;
      }
    } else if (node.nodeType === Node.ELEMENT_NODE) {
      Array.from(node.attributes).forEach(function(attr) {
        if (attr.name.startsWith('v-')) {
          if (attr.name === 'v-model') {
            node.value = '';
            node.addEventListener('input', function(event) {
              vm[attr.value] = event.target.value;
            });
          }
          node.removeAttribute(attr.name);
        }
      });
    }

    if (node.childNodes.length > 0) {
      compile(node.innerHTML);
    }
  });

  return element.innerHTML;
}

var vm = new Vue({
  el: '#app',
  data: {
    message: 'Hello, World!'
  },
  template: '<div><h1>{{message}}</h1></div>'
});

document.getElementById('app').innerHTML = compile(vm.template);


3. 你知道哪些vue3新特性

  • Composition API*

  • vue3的新特性组合式api,组合式不代表是函数式,也就是可以把多个功能组合在一起使用函数(没有明确的this指向,直接通过上下文使用了)的方式编写vue组件,最终可以组合在一起

  • 组合式api不是函数式编程(我们写成一个个的函数最终可以组合起来)

  • 组合式api(响应式部分 ref(), reactive(),生命周期钩子 onMounted(),onUnmanned()依赖注入‘provide()、inject()’)

  • SFC Composition API Syntax Sugar (<script setup>)

  • 单文件组合式api的语法糖

  • 代码写起来更简洁了不需要手动return, 而且不用借助代理对象性能好

  • Teleport

  • 类似于react中的Potal组件(传送门)、指定将内容渲染到某个容器中。

  • 也可以手动调用render方法来实现2aba981aa211449c4ed3441e08fa015.png

  • Fragments片段 vue3中允许组件中包含多个根节点,好处是无需无意义的包裹

  • Emits Component Option

  • Vue3中默认绑定的时间会绑定到根元素上,通过emits属性可以attrs中移除,这样只能通过emit来触发对应的事件

  • createRenderer API from @vue/runtime-core to create custom renderers

  • 创建渲染器,可以基于runtime-core 创建自定义的渲染器。可以实现跨平台渲染

  • SFC State-driven CSS Variables (v-bind in <style>)*

  • 在单文件组件中通过v-bind绑定css样式变量

  • bacground: v-bind(color);

  • SFC <style scoped> can now include global rules or rules that target only slotted content

  • 在作用域样式中我们能包含全局的规则和针对插槽的规则

  • image.png

  • image.png

  • image.png

  • Suspense 针对异步组件的优雅处理 image.png

4.Vue3对比Vue2的变化?

性能优化

  • 使用了Proxy替代了Object.defineProperty(为什么? 初始化需要递归给对象的所有的属性增添getter、setter方法,重写性能差,对新增的属性和删除的属性也无法监控,需要额外写一些vm.set,vm.set,vm.delete.对数组也能监控但是性能依然差。 Proxy即能监听到属性的添加和删除还不用添加getter和setter而且对数组也支持。 defineProperty不支持新的数据结构 map 和set 都不支持)
  • 模板编译优化:编译过程中给节点增加PatchFlag标记;对静态节点静态提升,函数缓存
  • diff算法(全量diff最长递增子序列,可以减少复用节点的移动)(非全量diff算法,只比较动态节点。通过PatchFlag更新动态的属性,减少递归操作)