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-show
dom元素显示与否,是通过css display
属性来控制的,并不会真正的消除dom元素节点!
vue2 生命周期
vue
中的生命周期就是vue组件
实例从创建到销毁
的一个过程!由以下
8个钩子函数
可以提现生命周期的变化!
创建阶段
beforeCreate
- 在创建实例时会调用该函数,此时实例的
属性
和方法
都还没有初始化
。
- 在创建实例时会调用该函数,此时实例的
created
- 在实例创建完成后立即调用。
- 此时实例的
属性
和方法
都已经初始化,$el
已经被创建
。
挂载阶段
beforeMount
此时组件的模板编译已经完成,但尚未挂载到 DOM 上。
mounted
- 实例被挂载后调用。
- DOM 已经创建并插入到页面中。
- 此时可以
进行 DOM 操作
或执行依赖于 DOM 的操作
。
更新阶段
beforeUpdate
- 数据更新时调用,发生在
虚拟DOM打
补丁之前。 - 此时
可以访问更新前的DOM,适用于获取更新前的状态
。
- 数据更新时调用,发生在
updated
- 由于数据更改导致的虚拟 DOM 重新渲染和打补丁,在这之后会调用该钩子。
- 当这个钩子被调用时,
组件 DOM 已经更新
,所以现在可以执行依赖于 DOM 的操作。
销毁阶段
beforeDestory
- 实例销毁之前调用。
- 此时实例仍然完全可用。
destoryed
Vue 实例销毁后
调用。- 调用后,所有的事件监听器会被移除,所有的子实例也会被销毁。
- 该钩子在服务器端渲染期间不被调用。
注意事项
在 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" x
Vue
无法检测到通过索引直接设置数组元素的变化。 arr[5].name = "zhangsan" x
Vue
无法检测到数组长度的变化。 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
有三个属性包括include
exclude
max
!
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为对象
`时,则会`导致实例之间数据共享
,造成数据混乱
`,因此必须为`函数并且返回一个新的对象
`。