Vue2、Vue3面试题

掌握原理 响应式原理、异步更新 本节需要掌握vue2、vue3各自的响应式原理、vue2响应式原理的弊端/为何改进、如何收集依赖、何时触发依赖实现更新、异步更新机制是什么/优点

  • vue2实现响应式:
    • 原理:
      • 实例初始化时遍历data里的对象所有的property,并使用Object.defineProperty把这些property全部转为getter/setter,访问对象的属性时getter函数触发并通过dep.depend()收集依赖,修改对象属性的值时setter函数触发并通过dep.notify()通知watcher
      • 每个组件实例都对应一个watcher实例,它会在组件渲染的过程中把“接触”过的数据property记录为依赖。之后当依赖项的setter触发时,会通知 watcher,从而使它关联的组件重新渲染
      • vue2对对象的检测是,为对象的每一个属性绑定Object.defineProperty的get、set方法(包括嵌套属性,需要递归)从而实现响应式,而对数组的检测,是对数组变更方法进行重载(7个方法,push()、pop()、shift()、unshift()、splice()、sort()、reverse())
      • Vue无法检测property的动态添加或移除,因此初始化过后可以使用Vue.set(object, propertyName, value) 方法向嵌套对象添加响应式property
    • 特点:
      • 无法检测数组元素的新增或删除,需要对数组方法进行重写,无法检测数组长度修改
      • 无法检测到对象属性的添加或删除
      • 必须遍历对象的每个属性,为每个属性都进行get、set拦截
      • 必须深层遍历嵌套的对象
  • vue3实现响应式:
    • 原理:
      • vue3中用ref、reactive来创建响应式数据,ref都是RefImpl类的实例,在RefImpl类的get中收集依赖,在set中触发依赖,而reactive都是Proxy的实例,用reactive创建数据后返回的是proxy代理对象,之后便可在该代理对象上进行操作,Proxy在代理对象前架设了一层拦截器来控制对象的操作,Proxy支持13种拦截方式
      • 一般ref用来包裹基本数据类型,reactive用来包裹引用数据类型,如果用ref包裹引用数据类型,vue还是会将该引用数据类型先用reactive处理后再用ref处理,而如果用reactive包裹基本数据类型会直接报错,且reactive默认为深层监听
      • computed的处理方式和ref基本一致,computed里的参数为函数时,默认为getter函数且返回一个不可变的响应式ref对象,参数为对象时接受一个具有get、set函数的对象,用来创建可写的ref对象
    • 特点:
      • 针对整个对象,而不是对象的某个属性(浅层,仍需递归)
      • 仍需要将嵌套对象进行遍历为响应式(在get里递归调用Proxy并返回,同vue2)
      • 不需要对数组的方法进行重载,Proxy支持13种拦截操作(解决vue2问题)
      • 可以检测到对象属性的添加或删除(解决vue2问题)
    注:响应式处理发生在生命周期beforeCreate、created之间
  • 异步更新:
    • 异步更新,只要侦听到数据变化,Vue 将开启一个队列,并缓冲在同一事件循环(‘tick’)中发生的所有数据变更。在缓冲时去除重复数据对于避免不必要的计算和DOM操作是非常重要的。如果同一个 watcher 被多次触发,只会被推入到队列中一次。然后,在下一个的事件循环“tick”中,Vue刷新队列并执行实际 (已去重的) 工作
Vue2、Vue3面试题
文章图片

生命周期 本节需要掌握生命周期的整个流程、包括各个声明周期的前后各做什么事情、AST树、虚拟dom、vue2/vue3分别如何处理响应式数据、异步更新过程和优点,以及几个重要的阶段:首次可调用data methods..的位置?哪里生成的AST树?在哪里生成的render函数?在哪里执行的render函数?哪里生成的vnode(虚拟dom)?哪里进行的diff算法?在beforeMount阶段获取refs结果是什么?首次可通过$refs获取模板引用在哪?在beforeUpdate阶段获取$refs结果是什么?nextTick是什么?组件在首次加载时会触发哪些钩子、在运行阶段时会触发哪些钩子?
vue声明周期流程图
创建vue根实例(vue2:new Vue(),vue3为Vue.createApp())
初始化events和生命周期(vue2:events包括$on、$off、$emit、$listener)
beforeCreate
初始化注入和响应式
(vue2:provide、inject、props、data、computed、watch、methods,vue3:ref、reactive..)
created(第一次可以使用data、methods..数据)
判断有没有el选项作为挂载点, 没有就使用$mount指定的元素作为挂载点,
if(render){ 执行下一个阶段 }
else if(template){ 将template编译成render函数,执行下一个阶段 }
else { 把挂载点的内容作为template编译成render函数,执行下一个阶段 }
(模板语法/template -> 抽象语法树/AST -> render函数)
beforemount(此时的$refs还是个空的、vnode挂载前的)
调用render函数,生成虚拟dom,之前的挂载点el元素将被新生成的$el替代掉,$refs就是在这一步被解析出来的
mounted(第一次可以操作dom、获取$refs模板引用)
响应式数据发生变化触发beforeUpdate
beforeUpdate(还可拿到更新前的dom/数据,也可在这里继续对数据修改,需知异步更新过程)
重新触发render函数生成新的vnode、与老的vnode进行diff算法比较且更新到真实dom上
updated
组件卸载被触发
beforeUnmount(实例功能完全正常,应当在卸载前移除手动添加的监听器,如clearTimeout、clearInterval等清除操作)
卸载完成,data、methods..都不可再访问了
unMounted
一些细节:
  • vue3的生命周期函数的几点小变化:
    • 所有的生命周期现在在setup里面都是用onX导入后使用(摇树)
    • beforeDestroy改名为beforeUnmount,destroyed改名为unmounted
    • 没有onBeforeCreate、onCreated这两个钩子了,因为setup就是初始化过程,即以前需要写在onBeforeCreate、onCreated钩子里的操作,现在写在setup就好了
  • 获取$refs模板引用最早是在mounted,因为$refs是挂载后的产物,在mounted才挂载完成,在那之前获取$refs是空的、没有意义的,而对于运行阶段的beforeUpdate获取$refs那就是更新前的旧的数据
  • nextTick在dom挂载完成或dom更新完成后立即调用(同样类似还有watch、watchEffect的{flush:post})