本文最后更新于 2024-10-25,文章内容可能已经过时。

https://zhuanlan.zhihu.com/p/644543319更多面试题!

vue组件通信方式

  1. 父传子 : props 属性可用来向子组件单项数据传递, 也可以通过$refs 形式获取子组件实例,调用子组件方法进行传递!

  2. 子传父 : $emit / $on 在子组件中,通过 $emit 方式派发事件并传递参数,在父组件中,通过$on方式来接受派发事件和参数!

  3. 兄弟组件 : 通过EventBus 事件总线,或者通过 vuex 全局状态管理!

  4. 父传子孙 : provide / inject ,或者以兄弟组件方式传递也可以!

当然,在组件传递,也可以通过 $root 或者 $parent 或者 $children$attrs方式,传递通信!

v-if 和 v-for优先方式

当两个指令同时出现在一个元素上时,v-for 指令优先级比v-if高,会先解析v-for然后在解析v-if,所以在进行列表渲染时,会将每个元素转换为虚拟节点,然后在对每一个节点进行判断!

<template>
  <ul>
    <li v-for="item in items" :key="item.id" v-if="item.active">
      {{ item.name }}
    </li>
  </ul>
</template>

<script>
export default {
  data() {
    return {
      items: [
        { id: 1, name: 'Alice', active: true },
        { id: 2, name: 'Bob', active: false },
        // ...
      ]
    };
  }
};
</script>

v-if 和 v-show区别

v-if条件渲染 ,当条件为false 时,不会生成dom元素!

v-show dom元素显示与否,是通过css display属性来控制的,并不会真正的消除dom元素节点!

vue2 生命周期

vue中的生命周期就是vue组件实例从创建到销毁的一个过程!

由以下8个钩子函数可以提现生命周期的变化!

创建阶段

  1. beforeCreate
    • 在创建实例时会调用该函数,此时实例的属性方法都还没有初始化
  2. created
    • 在实例创建完成后立即调用。
    • 此时实例的属性方法都已经初始化,$el 已经被创建

挂载阶段

  1. beforeMount
    • 此时组件的模板编译已经完成,但尚未挂载到 DOM 上。
  2. mounted
    • 实例被挂载后调用。
    • DOM 已经创建并插入到页面中。
    • 此时可以进行 DOM 操作执行依赖于 DOM 的操作

更新阶段

  1. beforeUpdate
    • 数据更新时调用,发生在虚拟DOM打补丁之前。
    • 此时可以访问更新前的DOM,适用于获取更新前的状态
  2. updated
    • 由于数据更改导致的虚拟 DOM 重新渲染和打补丁,在这之后会调用该钩子。
    • 当这个钩子被调用时,组件 DOM 已经更新,所以现在可以执行依赖于 DOM 的操作。

销毁阶段

  1. beforeDestory
    • 实例销毁之前调用。
    • 此时实例仍然完全可用。
  2. destoryed
    • Vue 实例销毁后调用。
    • 调用后,所有的事件监听器会被移除,所有的子实例也会被销毁。
    • 该钩子在服务器端渲染期间不被调用。

注意事项

  • 在 Vue 3 中,生命周期钩子有所变化,例如 beforeCreatecreated 被合并为 setup 函数,而 beforeMountmounted 等其他钩子仍然存在,但使用方式略有不同。

keep-alive

Vue 2 中,<keep-alive> 是一个抽象组件,用于保持组件状态或避免重新渲染。当包裹动态组件或组件的切换过程中,<keep-alive> 可以缓存不活动的组件实例,而不是销毁它们。当这些组件再次需要渲染时,会从缓存中直接读取,而不是重新创建实例

<keep-alive> 提供了两个生命周期钩子函数,这两个钩子函数只会在特定条件下被调用:

  1. activated
    • 当组件被激活时调用。
    • 该钩子在 <keep-alive> 包裹的组件激活时调用。
    • 例如,当一个路由组件被包含在 <keep-alive> 中,并且从一个不同的路由导航到该组件时,activated 钩子会被调用
  2. deactivated
    • 当组件被停用时调用。
    • 该钩子在 <keep-alive> 包裹的组件停用时调用。
    • 例如,当一个路由组件被包含在 <keep-alive> 中,并且从该组件导航到另一个不同的路由时,deactivated 钩子会被调用。
<template>
  <div>
    <keep-alive>
      <router-view v-if="$route.meta.keepAlive"></router-view>
    </keep-alive>
    <router-view v-if="!$route.meta.keepAlive"></router-view>
  </div>
</template>

vue双向绑定语法糖

vue 中,双向绑定即是视图data之间的一个数据绑定关系 ,双向绑定通过v-model 形式表示,且只能运用在可交互表单上

v-model 的表现形式,是通过数据绑定+事件监听形式来完成的!

<input v-model="data" />

<input :value="data" @input="(val) => data = val" />

vue 响应式原理

数据双向绑定(响应式)就是 视图数据之间的`绑定关系`,当`数据发生变化`时,`视图也会发生变化`,反之亦然。

在`初始化vue实例`时,vue会将`data中的属性转换为响应式`,当某个`属性获取或者更新时`,会`触发监听`,并`获取该属性对应的依赖,然后通知依赖更新视图`。

vue2 defineProperty

在 Vue 中,只有对象数组这样的引用类型数据可以被 Vue 转换成响应式数据。基本类型(如字符串、数字、布尔值等)是不具备响应式特性的。

vue2 中响应式是通过 Object.defineProperty 方法实现的, 且允许 Vue 实例在数据变化时自动更新 DOM !

Object.defineProperty() JavaScript 中的一个内置方法,用于在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回该对象。这个方法允许精确地添加或修改对象的属性,并控制这些属性的行为

这个方法允许你精确地控制对象的属性,包括它们的读取、写入、枚举性和配置性。Vue 通过这个方法将数据对象的每个属性转换成一个带有 getter 和 setter 的属性,这样当属性被访问或修改时,Vue 可以检测到并作出响应

Object.defineProperty(obj, prop, descriptor)
  • obj:要在其上定义属性的对象。

  • prop:要定义或修改的属性的名称。

  • descriptor:将被定义或修改的属性的描述符。

配置项

就是给对象属性定一个规则,属性定默认值 属性是否可枚举 是否可编辑 可删除等相关约定配置!

  • value:属性的值,默认为 undefined

  • writable:如果为 true,属性的值可以被修改,默认为 false

  • enumerable:如果为 true,属性会被枚举在对象的枚举属性中,默认为 false

  • configurable:如果为 true,属性可以被删除,且这些特性可以被修改,默认为 false

getter 和 setter

getter负责依赖收集,setter 负责派发更新!响应式数据的初始化

  • get:一个给属性提供 getter 的方法,如果没有 getter 则为 undefined。深度监听

  • set:一个给属性提供 setter 的方法,如果没有 setter 则为 undefined

  • enumerableconfigurable 同数据描述符。

let person = {};

Object.defineProperty(person, 'name', {
  value: 'Alice',
  writable: true,
  enumerable: true,
  configurable: true
});

console.log(person.name); // 输出 'Alice'
person.name = 'Bob';
console.log(person.name); // 输出 'Bob'
响应式数据的初始化

当一个 Vue 实例被创建时,Vue 会遍历 data 选项中的所有属性,并使用 Object.defineProperty() 将它们转换成 getter/setter。这些 getter/setter使得 Vue 能够在属性被访问或修改时执行依赖收集和派发更新

依赖收集

Vue 使用一个观察者模式来实现依赖收集。每个组件实例都有一个对应的 watcher 实例,它会在组件渲染过程中记录依赖的响应式数据。当响应式数据变化时,Vue 会通知所有依赖于该数据的 watcher 实例,然后这些 watcher 实例会触发组件的重新渲染

派发更新

响应式数据被修改setter 会被触发Vue 会通知所有依赖于该数据的组件进行更新这个过程称为派发更新Vue 会使用一个队列来收集在同一个“nextick”内发生的所有的数据变更,然后在下一个“nextick”中批量更新视图,这样可以避免不必要的中间状态,提高性能。

异步更新队列

Vue 的更新是异步的。当数据变化时,Vue 不会立即更新 DOM,而是将观察到数据变化的组件标记为需要更新,并放入一个队列中。在下一个“nextick”中,Vue 执行更新操作,这有助于合并多个数据变化,只更新一次 DOM,从而提高性能。

深度监听

Vue 2 默认是深度监听的,即如果对象的属性也是对象么这个属性的属性也会被转换成响应式这样可以确保嵌套对象的响应式特性

限制
  • Vue 无法检测到对象属性的添加或删除。obj.name = "zhangsan" x

  • Vue 无法检测到通过索引直接设置数组元素的变化。 arr[5].name = "zhangsan" x

  • Vue 无法检测到数组长度的变化。 arr.length = newLength

为了克服这些限制,Vue 提供了 Vue.setVue.delete 方法,以及 vm.$setvm.$delete 实例方法来手动触发依赖更新。

数组响应式限制

在 Vue 2 中,数组的响应式处理与对象的处理方式有所不同,主要是因为 Object.defineProperty() 方法的限制Object.defineProperty() 方法用于定义对象的新属性或修改现有属性,并且可以精确控制属性的特性,如是否可枚举、是否可写等。然而,这个方法有一些固有的限制,特别是在处理数组时:

  1. 索引修改Object.defineProperty() 无法检测到通过索引直接设置数组元素的变化。例如,arr[0] = new_value 这样的操作不会触发 Vue 的响应式系统。

  2. 数组长度变化:同样,如果直接修改数组的长度(如 arr.length = new_length),Vue 也无法检测到这种变化。

  3. 数组方法:虽然 Vue 2 对一些数组方法(如 push, pop, shift, unshift, splice, sort, reverse)进行了特殊处理,使得它们能够触发视图更新,但这些方法之外的其他数组操作,如直接通过索引赋值,Vue 无法自动检测到。

为了在 Vue 2 中实现数组的响应式更新,Vue 提供了以下方法:

  • Vue.set (target, key/index, value):向响应式对象中添加一个属性,并确保这个新属性同样是响应式的,且触发视图更新。对于数组,可以使用 Vue.set 来添加新元素,例如 Vue.set(vm.items, indexOfItem, newValue)

  • vm.$set (target, key/index, value):这是 Vue.set 的实例方法版本,作用相同。

  • 数组变异方法:Vue 重写了数组的七个变异方法(push, pop, shift, unshift, splice, sort, reverse),使得这些方法在修改数组时能够触发视图更新。

vue3 proxy

Vue 3 使用了 ES6 的 Proxy 对象来实现响应式系统,而 Vue 2 使用的是 Object.defineProperty()Proxy 提供了更加强大和灵活的方式来拦截和定义对象的底层操作,包括属性访问、赋值、枚举、函数调用等

响应式原理
  1. 创建响应式对象:当使用 reactive 函数创建响应式对象时,Vue 3 会返回一个 Proxy 实例。这个 Proxy 实例包装了原始对象,并拦截了对其属性的访问和修改。

  2. 拦截器(Handler)Proxy 的拦截器定义了各种操作的处理函数,如 get(获取属性值)、set(设置属性值)、deleteProperty(删除属性)等。Vue 3 在这些处理函数中实现了依赖收集和触发更新的逻辑。

  3. 依赖收集:当访问响应式对象的属性时,get 拦截器会被触发。Vue 会检查当前的上下文(例如组件实例),并将当前的 effect(副作用函数,如组件渲染函数)与属性关联起来。这样,当属性值发生变化时,Vue 能够知道哪些 effect 需要重新运行。

  4. 触发更新:当响应式对象的属性被修改时,set 拦截器会被触发。Vue 会检查这个属性是否有相关的 effect,如果有,它会重新运行这些 effect,从而更新依赖于该属性的组件。

优点
  • 更好的性能Proxy 只在属性被访问或修改时触发,避免了 Vue 2 中需要遍历对象属性的性能开销。

  • 更好的兼容性Proxy 支持数组和 Map、Set 等数据结构的响应式处理,无需特殊处理。

  • 更细粒度的控制Proxy 提供了更细粒度的控制,可以拦截所有属性操作,而不仅仅是属性的读取和写入。

vue 模版的渲染原理

Vue.js模板渲染原理是其核心特性之一,它允许开发者使用声明式(template)的方式描述界面并将这些描述转换成实际的 DOM 更新

vue模版渲染原理分为以下几个步骤:

(template compiler) 模版编译 parse 解析语法树(生成虚拟dom),

虚拟dom真实dom对比,找到差异点,进行真实dom差异更新!

1. 模板解析

  • 模板(Template):开发者在 Vue 组件中编写的 HTML 模板。

  • 编译器(Compiler):Vue 提供了一个编译器,它将模板转换成 JavaScript 代码。这个编译过程可以是运行时编译(在浏览器中进行),也可以是构建时编译(在构建工具如 Webpack 中进行,通过 vue-loader)。

编译器会解析模板中的指令(如 v-bindv-ifv-for 等)、插值表达式(如 {{ message }})和特殊元素(如 <template><component> 等)。

2. 创建渲染函数

编译器将模板转换成一个渲染函数render function)。这个函数是一个 JavaScript 函数,它返回虚拟 DOM(Virtual DOM)节点,描述了应该渲染的界面结构。

3. 虚拟 DOM

  • 虚拟 DOM(Virtual DOM):Vue 使用虚拟 DOM 来表示真实 DOM 的结构。虚拟 DOM 是一个轻量级的 JavaScript 对象它描述了 DOM 的结构、属性、事件监听器等信息

虚拟 DOM 的好处是,当数据变化时,Vue 不需要直接操作真实 DOM,而是通过比较虚拟 DOM 树的差异来找出需要更新的部分,然后批量更新真实 DOM,这样可以提高性能。

4. 响应式系统与数据更新

  • 响应式系统:Vue 的响应式系统会追踪数据的变化,并在数据变化时通知相关的组件。

  • 数据更新:当响应式数据变化时,Vue 会重新执行渲染函数,生成新的虚拟 DOM 树。

5. DOM 更新

  • 差异比较(Diffing):Vue 使用一种高效的算法来比较新旧虚拟 DOM 树的差异找出最小的更新集

  • 更新真实 DOM根据差异比较的结果,Vue 会更新真实 DOM 中需要变化的部分。

6. 依赖收集与派发更新

  • 依赖收集:Vue 的响应式系统会追踪模板中使用的数据,并在数据变化时重新渲染组件。

  • 派发更新:当响应式数据变化时,Vue 会触发依赖于这些数据的组件的重新渲染。

Vue 的模板渲染原理结合了模板编译虚拟 DOM 响应式系统使得开发者可以使用声明式的方式编写界面,同时保持高效的性能。Vue 通过编译模板生成渲染函数,利用虚拟 DOM 来高效地更新真实 DOM,响应式系统则确保了数据变化时界面能够及时更新。

vue2 和 vue3区别

  1. 响应式系统重构: vue2是通过Object.defineProperty 实现响应式, vue3是通过Proxy实现响应式的!
    • vue2 无法监听对象和数组子项添加和删除,需要借助vm.set() 以及 数组编译方法()来实现!
    • vue3 proxy 可以很好的兼容 对象以及数组的响应式变化!
  2. composition api:
    • vue2 采用的是options api 其逻辑代码需要分散到对应模块当中(data methods computed`),可能导致代码逻辑维护难度增加!
    • vue3 采用的是composition api 组合式选项,将代码的逻辑组合在一起,让维护变得更加容易!
  3. 生命周期的变化:
    • vue2中的beforeCreate created两个钩子函数在vue3中被替代为setup函数, beforeDestory destory两个钩子函数替代为onBeforeUnmount 和 onUnmount,其余钩子函数前加入了前缀on!
  4. vue3中模版文件中可支持多个根标签vue2中仅支持一个根标签!
  5. 性能优化: 代码体积减少,缩小打包体积,提高渲染速度!
  6. 支持typescript语法!
  7. 新增异步组件(Suspense)和传送门(teleport)组件!

vue中 key 的作用

key vue中用来给虚拟dom做唯一标识,在dom发生变化时,可以通过key来有效进行新旧dom差异对比!

Vue中v-for中的key使用index会有什么问题

1. v-for中的key使用index会导致vuediff算法无法准确的识别节点是否有变化,从而渲染的开销会比较大!

2. index索引,是有序的,无论列表里的数据如何变化,其索引都是从 0 - 1 的一个过程,这导致dom更新时,无法准确的比对dom节点,从而导致结果输出错乱

vue中的 nextick

nextickvue异步执行回调函数,在数据更新之后dom更新之前调用!

更新dom操作是异步的,当dom发生变化时会被标记为'需要更新'并添加到异步队列,等到其它操作完成时,在进行更新操作!

vue中对组件的理解

组件就是对大型视图可复用模块结构的一个拆分将公共模块部分提取一个组件供其它视图逻辑公用提高开发效率降低耦合度!

vue中的 keep-alive

keep-alivevue中特有的组件,主要用来缓存组件状态,被缓存的组件会存放在内存中!

keep-alive 有三个属性包括 include exclude max!

include: 包含,可以为正则表达式,匹配的组件会被缓存!

exclude: 排除, 匹配的组件不会被缓存!

max: 缓存最大值!

keep-alive: 会多两个生命周期钩子函数分别为: activated(组件被激活时) deactivated(组件被销毁时)!

vue 中首屏加载优化

  1. 路由懒加载: 减少入口路由文件的体积!

  2. Gzip压缩: 对打包文件结果体积压缩!

  3. UI懒加载: ElementUI 组件库懒加载引入!

  4. 图片压缩: images-webpack-loader!

vue-router路由模式的实现方式

vue-router中一共有三种模式分别为( hash history abstract )

  1. hash模式:
    • 利用 URLhash 部分(即 # 后面的部分)来实现前端路由。
    • hash 发生变化时,浏览器不会重新加载页面,可以通过监听 window.onhashchange 事件来捕捉 hash 的变化。
    • vue-router 会根据 hash 的变化来匹配对应的路由规则,并渲染相应的组件。
  2. history模式:
    • 利用 HTML5History API,包括 pushStatereplaceState 方法,来改变浏览器的 URL 而不重新加载页面。
    • pushStatereplaceState 允许我们添加和修改历史记录条目,vue-router 使用这些 API 来实现路由的前进和后退功能。
    • hash 模式类似,vue-router 会监听 popstate 事件来响应浏览器的前进和后退操作
    • 为了确保服务器能正确处理所有路由,通常需要服务器配置,使得所有路由请求都返回应用的入口页面(通常是 index.html)。

组件中 data 为什么是一个函数?

vue 开发中,是`多组件开发`的,而`每个组件都对应一个vue实例`,且`每个实例对应的一个构造函数`,如果一个`组件中嵌套了子组件`,且`data为对象`时,则会`导致实例之间数据共享造成数据混乱`,因此必须为`函数并且返回一个新的对象`。