前端八股文自救指南——Vue——Day3

35 阅读7分钟

Vue

双向绑定

双向数据绑定是指数据的变化会自动更新到视图,视图的变化也会自动更新到数据。在Vue2中,双向数据绑定主要基于 Object.defineProperty() 实现,以 v-model 指令为例,其原理如下:

  1. 数据劫持:使用 Object.defineProperty() 对数据对象的属性进行劫持,当属性值发生变化时,会触发 setter 方法。
  2. 发布 - 订阅模式:每个数据属性都有一个对应的 Dep(依赖收集器)对象,当有依赖(如视图中的 DOM 节点)访问该属性时,会将依赖添加到 Dep 的订阅者列表中。当属性值改变时,Dep 会通知所有订阅者进行更新。
  3. DOM 监听:对于表单元素,通过监听其 input 等事件,当用户输入时,更新对应的数据属性。

defineProperty() 的缺点

  1. 无法检测对象属性的添加和删除Object.defineProperty() 只能劫持对象已有的属性,对于新增或删除的属性无法自动劫持,需要使用 Vue.set 或 Vue.delete 等方法来手动处理。
  2. 无法检测数组的部分方法:虽然可以劫持数组的 pushpop 等变异方法,但对于通过索引直接修改数组元素或修改数组长度的操作无法检测,需要使用 Vue 提供的特定方法来更新数组。
  3. 深层嵌套对象需要递归劫持:对于深层嵌套的对象,需要递归地对每个属性进行劫持,性能开销较大。

Vue3的区别

  1. 响应式原理:Vue 3 使用 Proxy 代替 Object.defineProperty() 实现响应式。Proxy 可以劫持整个对象,能够检测对象属性的添加和删除,并且不需要递归劫持,性能更好。
  2. 兼容性Object.defineProperty() 是 ES5 的特性,兼容性较好,但功能有限;Proxy 是 ES6 的特性,在一些旧浏览器中不支持。
  3. 代码复杂度:Vue 3 的响应式系统代码更简洁,因为 Proxy 可以直接对对象进行拦截,而不需要像 Object.defineProperty() 那样对每个属性进行单独处理。

Vue 是如何收集依赖的

1. 创建响应式对象

使用 reactive 函数创建响应式对象,它会返回一个 Proxy 对象。

reactive 函数内部会创建一个 Proxy 对象,拦截对象的 get 和 set 操作。

2. 创建副作用函数

使用 effect 函数创建副作用函数,在副作用函数中访问响应式对象的属性。

当调用 effect 函数时,会将传入的函数封装成一个 Effect 实例,并立即执行该函数。

3. 依赖收集

在副作用函数执行过程中,当访问响应式对象的属性时,会触发 Proxy 的 get 拦截器。

在 track 函数中,会维护一个全局的 targetMap,它是一个 WeakMap,用于存储每个响应式对象对应的依赖信息。targetMap 的键是响应式对象,值是一个 Map,该 Map 的键是对象的属性名,值是一个 Set,存储依赖于该属性的 Effect

4. 触发更新

当响应式对象的属性值发生变化时,会触发 Proxy 的 set 拦截器。

在 trigger 函数中,会从 targetMap 中找到对应的依赖集合,然后遍历该集合,重新执行每个 Effect

slot 是什么?有什么作用?原理是什么?

  1. 定义slot 即插槽,是 Vue 提供的一种内容分发机制,用于在组件中预留位置,让父组件可以向子组件传递内容。分为:默认插槽、具名插槽、作用域插槽。
  • 默认插槽:又名匿名插槽,当slot没有指定name属性值的时候一个默认显示插槽,一个组件内只有有一个匿名插槽。
  • 具名插槽:带有具体名字的插槽,也就是带有name属性的slot,一个组件可以出现多个具名插槽。
  • 作用域插槽:默认插槽、具名插槽的一个变体,可以是匿名插槽,也可以是具名插槽,该插槽的不同点是在子组件渲染作用域插槽时,可以将子组件内部的数据传递给父组件,让父组件根据子组件的传递过来的数据决定如何渲染该插槽。
  1. 作用

    • 提高组件的复用性:通过插槽,组件可以根据不同的使用场景插入不同的内容。
    • 实现组件的定制化:父组件可以根据需要自定义子组件的部分内容。
  2. 原理

    • 编译阶段:Vue 编译器会将组件模板中的 slot 标签编译成特殊的占位符。
    • 渲染阶段:在渲染子组件时,会将父组件传递的内容替换到 slot 占位符的位置。

对 keep-alive 的理解,它是如何实现的,具体缓存的是什么?

  1. 理解keep-alive 是 Vue 提供的一个内置组件,用于缓存动态组件或路由组件,避免重复渲染,提高性能。

  2. 实现原理

    • 缓存机制keep-alive 内部维护一个 cache 对象,用于存储缓存的组件实例。当组件被 keep-alive 包裹时,会将组件实例缓存到 cache 中。
    • 生命周期钩子:被缓存的组件在激活和失活时会触发 activated 和 deactivated 生命周期钩子,而不是 mounted 和 destroyed
  3. 缓存内容keep-alive 缓存的是组件的实例,包括组件的状态和 DOM 结构。

Vue 模版编译原理

  1. 解析阶段:将模板字符串解析成抽象语法树(AST),Vue 使用正则表达式和状态机来实现解析。
  2. 优化阶段:对 AST 进行优化,标记出静态节点(在渲染过程中不会发生变化的节点),减少重新渲染的开销。
  3. 生成阶段:将优化后的 AST 转换为渲染函数,渲染函数返回虚拟 DOM。

虚拟 DOM

对虚拟 DOM 的理解

虚拟 DOM 是一种轻量级的 JavaScript 对象,它是真实 DOM 的抽象表示。虚拟 DOM 通过 JavaScript 对象的形式来描述真实 DOM 的结构和属性,通过对比新旧虚拟 DOM 的差异,只更新需要更新的真实 DOM 部分,从而提高渲染性能。

虚拟 DOM 不一定比真实 DOM 快

虚拟 DOM 的优势在于减少不必要的 DOM 操作,在数据变化频繁的情况下,通过批量更新可以提高性能。但在以下情况下,虚拟 DOM 可能并不比真实 DOM 快:

  1. 简单场景:对于简单的页面,直接操作真实 DOM 可能更高效,因为虚拟 DOM 需要额外的计算和对比过程。
  2. 首次渲染:首次渲染时,虚拟 DOM 需要先创建虚拟节点,再将其转换为真实 DOM,可能会比直接操作真实 DOM 慢。
虚拟 DOM 解析过程
  1. 创建虚拟 DOM:根据组件的模板和数据,通过渲染函数创建虚拟 DOM 对象。
  2. 对比差异:当数据发生变化时,创建新的虚拟 DOM 对象,并与旧的虚拟 DOM 对象进行对比,找出差异。
  3. 更新真实 DOM:根据差异,只更新需要更新的真实 DOM 部分。

DIFF 算法原理

  1. 同层比较:Vue 的 DIFF 算法采用同层比较的策略,只比较同一层级的节点,不会跨层级比较。

  2. key 的作用:为节点添加唯一的 key 可以帮助 DIFF 算法更高效地识别节点的变化,避免不必要的节点重新创建。

  3. 比较策略

    • 节点类型不同:直接替换节点。
    • 节点类型相同:比较节点的属性和子节点,只更新有差异的部分。