vue相关面试题
本文最后更新于 2024-10-25,文章内容可能已经过时。
vue组件通信方式
父传子:props属性可用来向子组件单项数据传递, 也可以通过$refs形式获取子组件实例,调用子组件方法进行传递!子传父:$emit / $on在子组件中,通过$emit方式派发事件并传递参数,在父组件中,通过$on方式来接受派发事件和参数!兄弟组件: 通过EventBus事件总线,或者通过vuex全局状态管理!父传子孙: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-showdom元素显示与否,是通过css display属性来控制的,并不会真正的消除dom元素节点!
vue2 生命周期
vue中的生命周期就是vue组件实例从创建到销毁的一个过程!由以下
8个钩子函数可以提现生命周期的变化!
创建阶段
beforeCreate- 在创建实例时会调用该函数,此时实例的
属性和方法都还没有初始化。
- 在创建实例时会调用该函数,此时实例的
created- 在实例创建完成后立即调用。
- 此时实例的
属性和方法都已经初始化,$el已经被创建。
挂载阶段
beforeMount此时组件的模板编译已经完成,但尚未挂载到 DOM 上。
mounted- 实例被挂载后调用。
- DOM 已经创建并插入到页面中。
- 此时可以
进行 DOM 操作或执行依赖于 DOM 的操作。
更新阶段
beforeUpdate- 数据更新时调用,发生在
虚拟DOM打补丁之前。 - 此时
可以访问更新前的DOM,适用于获取更新前的状态。
- 数据更新时调用,发生在
updated- 由于数据更改导致的虚拟 DOM 重新渲染和打补丁,在这之后会调用该钩子。
- 当这个钩子被调用时,
组件 DOM 已经更新,所以现在可以执行依赖于 DOM 的操作。
销毁阶段
beforeDestory- 实例销毁之前调用。
- 此时实例仍然完全可用。
destoryedVue 实例销毁后调用。- 调用后,所有的事件监听器会被移除,所有的子实例也会被销毁。
- 该钩子在服务器端渲染期间不被调用。
注意事项
在 Vue 3 中,生命周期钩子有所变化,例如
beforeCreate和created被合并为setup函数,而beforeMount和mounted等其他钩子仍然存在,但使用方式略有不同。
keep-alive
在
Vue 2中,<keep-alive> 是一个抽象组件,用于保持组件状态或避免重新渲染。当包裹动态组件或组件的切换过程中,<keep-alive> 可以缓存不活动的组件实例,而不是销毁它们。当这些组件再次需要渲染时,会从缓存中直接读取,而不是重新创建实例。
<keep-alive>提供了两个生命周期钩子函数,这两个钩子函数只会在特定条件下被调用:
activated- 当组件被激活时调用。
- 该钩子在
<keep-alive>包裹的组件激活时调用。 - 例如,当一个路由组件被包含在
<keep-alive>中,并且从一个不同的路由导航到该组件时,activated 钩子会被调用。
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。enumerable和configurable同数据描述符。
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" xVue
无法检测到通过索引直接设置数组元素的变化。 arr[5].name = "zhangsan" xVue
无法检测到数组长度的变化。 arr.length = newLength为了克服这些限制,Vue 提供了
Vue.set和Vue.delete方法,以及vm.$set和vm.$delete实例方法来手动触发依赖更新。
数组响应式限制
在 Vue 2 中,数组的响应式处理与对象的处理方式有所不同,主要是因为
Object.defineProperty() 方法的限制。Object.defineProperty()方法用于定义对象的新属性或修改现有属性,并且可以精确控制属性的特性,如是否可枚举、是否可写等。然而,这个方法有一些固有的限制,特别是在处理数组时:
索引修改:
Object.defineProperty()无法检测到通过索引直接设置数组元素的变化。例如,arr[0] = new_value这样的操作不会触发 Vue 的响应式系统。数组长度变化:同样,如果
直接修改数组的长度(如arr.length = new_length),Vue 也无法检测到这种变化。数组方法:虽然 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 提供了更加强大和灵活的方式来拦截和定义对象的底层操作,包括属性访问、赋值、枚举、函数调用等。
响应式原理
创建响应式对象:当使用
reactive函数创建响应式对象时,Vue 3 会返回一个Proxy实例。这个Proxy实例包装了原始对象,并拦截了对其属性的访问和修改。拦截器(Handler):
Proxy的拦截器定义了各种操作的处理函数,如get(获取属性值)、set(设置属性值)、deleteProperty(删除属性)等。Vue 3 在这些处理函数中实现了依赖收集和触发更新的逻辑。依赖收集:当访问响应式对象的属性时,
get拦截器会被触发。Vue 会检查当前的上下文(例如组件实例),并将当前的effect(副作用函数,如组件渲染函数)与属性关联起来。这样,当属性值发生变化时,Vue 能够知道哪些effect需要重新运行。触发更新:当响应式对象的属性被修改时,
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-bind、v-if、v-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区别
响应式系统重构: vue2是通过Object.defineProperty实现响应式, vue3是通过Proxy实现响应式的!- vue2 无法监听对象和数组子项添加和删除,需要借助
vm.set() 以及 数组编译方法()来实现! - vue3
proxy可以很好的兼容 对象以及数组的响应式变化!
- vue2 无法监听对象和数组子项添加和删除,需要借助
composition api:- vue2 采用的是
options api其逻辑代码需要分散到对应模块当中(data methods computed`),可能导致代码逻辑维护难度增加! - vue3 采用的是
composition api组合式选项,将代码的逻辑组合在一起,让维护变得更加容易!
- vue2 采用的是
生命周期的变化:- vue2中的
beforeCreate created两个钩子函数在vue3中被替代为setup函数,beforeDestory destory两个钩子函数替代为onBeforeUnmount 和 onUnmount,其余钩子函数前加入了前缀on!
- vue2中的
vue3中模版文件中可支持多个根标签,vue2中仅支持一个根标签!性能优化: 代码体积减少,缩小打包体积,提高渲染速度!- 支持
typescript语法! - 新增异步组件(
Suspense)和传送门(teleport)组件!
vue中 key 的作用
key在vue中用来给虚拟dom做唯一标识,在dom发生变化时,可以通过key来有效进行新旧dom差异对比!
Vue中v-for中的key使用index会有什么问题
1.
v-for中的key使用index会导致vue的diff算法无法准确的识别节点是否有变化,从而渲染的开销会比较大!2.
index是索引,是有序的,无论列表里的数据如何变化,其索引都是从0 - 1的一个过程,这导致dom更新时,无法准确的比对dom节点,从而导致结果输出错乱。
vue中的 nextick
nextick是vue中异步执行回调函数,在数据更新之后,dom更新之前调用!
更新dom操作是异步的,当dom发生变化时会被标记为'需要更新'并添加到异步队列,等到其它操作完成时,在进行更新操作!
vue中对组件的理解
组件就是对大型视图可复用模块结构的一个拆分,将公共模块部分提取一个组件,供其它视图逻辑公用,提高开发效率,降低耦合度!
vue中的 keep-alive
keep-alive是vue中特有的组件,主要用来缓存组件状态,被缓存的组件会存放在内存中!
keep-alive有三个属性包括includeexcludemax!
include:包含,可以为正则表达式,匹配的组件会被缓存!
exclude:排除, 匹配的组件不会被缓存!
max:缓存最大值!
keep-alive: 会多两个生命周期钩子函数分别为: activated(组件被激活时) deactivated(组件被销毁时)!
vue 中首屏加载优化
路由懒加载: 减少入口路由文件的体积!Gzip压缩: 对打包文件结果体积压缩!UI懒加载: ElementUI 组件库懒加载引入!图片压缩: images-webpack-loader!
vue-router路由模式的实现方式
vue-router中一共有三种模式分别为(hash history abstract)
hash模式:- 利用
URL的hash部分(即#后面的部分)来实现前端路由。 - 当
hash发生变化时,浏览器不会重新加载页面,可以通过监听window.onhashchange事件来捕捉 hash的变化。 vue-router会根据 hash 的变化来匹配对应的路由规则,并渲染相应的组件。
- 利用
history模式:- 利用
HTML5的History API,包括pushState和replaceState方法,来改变浏览器的 URL 而不重新加载页面。 pushState和replaceState允许我们添加和修改历史记录条目,vue-router使用这些 API 来实现路由的前进和后退功能。- 与
hash模式类似,vue-router会监听popstate事件来响应浏览器的前进和后退操作。 - 为了确保服务器能正确处理所有路由,通常需要服务器配置,使得所有路由请求都返回应用的入口页面(通常是 index.html)。
- 利用
组件中 data 为什么是一个函数?
在
vue开发中,是`多组件开发`的,而`每个组件都对应一个vue实例`,且`每个实例对应的一个构造函数`,如果一个`组件中嵌套了子组件`,且`data为对象`时,则会`导致实例之间数据共享,造成数据混乱`,因此必须为`函数并且返回一个新的对象`。