1、vue2和vue3的生命周期
vue2:
-
创建阶段(Initialization)
-
beforeCreate
:实例初始化之后,数据观测和事件配置之前调用,在当前阶段data、methods、computed以及watch上的数据和方法都不能被访问。。 -
created
:实例创建完成,数据观测和事件配置已完成,但 DOM 还未生成,也就是可以使用数据,更改数据,在这里更改数据不会触发updated函数。可以做一些初始数据的获取,在当前阶段无法与Dom进行交互,如果非要想,可以通过vm.$nextTick来访问Dom。
-
-
挂载阶段(Mounting)
-
beforeMount
:在挂载开始之前调用,此时模板编译已完成,但尚未将 DOM 插入页面,在此时也可以对数据进行更改,不会触发updated。 -
mounted
:真实的Dom挂载完毕,数据完成双向绑定,可以访问到Dom节点,使用$refs属性对Dom进行操作。
-
-
更新阶段(Updating)
-
beforeUpdate
:发生在更新之前,也就是响应式数据发生更新,虚拟dom重新渲染之前被触发,你可以在当前阶段进行更改数据,不会造成重渲染。 -
updated
:发生在更新完成之后,当前阶段组件Dom已完成更新。要注意的是避免在此期间更改数据,因为这可能会导致无限循环的更新。
-
-
销毁阶段(Destruction)
-
beforeDestroy
:实例销毁之前调用,此时实例仍然完全可用,可以在这时进行善后收尾工作,比如清除计时器。 -
destroyed
:实例销毁后调用,所有的事件监听器和子实例已被移除。
-
vue3:
-
beforeDestroy
和destroyed
被重命名:-
beforeDestroy
改为beforeUnmount
。 -
destroyed
改为unmounted
。
-
-
新增了
onRenderTracked
和onRenderTriggered
(仅用于调试):-
onRenderTracked
:跟踪虚拟 DOM 重新渲染时的依赖。 -
onRenderTriggered
:当虚拟 DOM 重新渲染被触发时调用。
-
总结:
-
Vue 2 和 Vue 3 的生命周期钩子基本一致,但 Vue 3 对销毁阶段的钩子进行了重命名。
-
Vue 3 的 Composition API 提供了更灵活的生命周期钩子,名称以
on
开头。 -
在 Vue 3 中,推荐使用 Composition API 的钩子,尤其是在复杂组件中。
2、虚拟dom的实现原理
虚拟 DOM是通过差异计算,只更新需要变更的部分,避免频繁操作实际 DOM 带来的性能开销。
1、初始渲染:当应用首次加载时,虚拟 DOM 会通过 JavaScript 对象的形式构建整个页面的结构,这个 JavaScript 对象被称为虚拟 DOM 树。此过程称为初始渲染。
2、用户交互:当用户与应用进行交互,例如点击按钮或输入文本框时,会触发应用状态的更改。
3、虚拟 DOM 更新:当应用状态发生变化时,虚拟 DOM 会重新构建一颗新的虚拟 DOM 树。
4、差异计算:新旧虚拟 DOM 树会进行比较,找出两者之间的差异。这个过程称为差异计算。为了提高效率,虚拟 DOM 通常采用了高效的算法进行差异计算,例如虚拟 DOM 树的深度优先遍历算法。
5、实际 DOM 更新:将差异计算的结果应用到实际的 DOM 上,只更新需要变更的部分,而不是整个页面。这个过程称为实际 DOM 更新。由于实际 DOM 更新是相对消耗性能的操作,通过减少实际 DOM 更新的次数,可以提高应用的性能。
3、vue的响应式机制
需要了解他的响应式机制,就需要清楚几个概念依赖追踪和虚拟dom
-
依赖追踪(Dependency Tracking):Vue内部使用依赖追踪来知道哪些组件依赖于哪些数据。这允许Vue在数据变化时准确地知道需要更新哪些组件。
-
虚拟DOM(Virtual DOM):Vue使用虚拟DOM来高效地更新DOM。当数据变化时,Vue会生成一个新的虚拟DOM树,与之前的虚拟DOM树进行比较,并只更新实际需要更改的部分,从而减少DOM操作,提高性能。
Vue的响应式系统基于Object.defineProperty或Proxy来劫持数据对象的属性。当访问或修改这些属性时,Vue能够捕捉到这些操作并触发相应的更新操作。
比如你在组件中log一下响应式数据,Vue会捕获这次读取操作,然后建立一个依赖关系,将这个组件与这个数据属性关联起来。当数据属性发生变化时,Vue会知道哪些组件需要进行更新,并自动更新相应的DOM元素。
4、如何理解MVVM
MVVM(Model-View-ViewModel,模型-视图-视图模型),vue就是mvvm架构模式
- Model(模型):表示应用程序的数据和业务逻辑。
- View(视图):呈现模型的数据,并与用户进行交互。
- ViewModel(视图模型):负责将模型的数据转换为视图可以使用的形式,并处理用户交互逻辑。
在MVVM中,引入了数据绑定的概念,视图和视图模型之间通过数据绑定实现自动更新,减少了视图和视图模型之间的代码交互。
MVVM中视图模型负责将模型的数据转换为视图可以使用的形式,而在MVC和MVP中,这个责任通常由控制器/展示器来完成。
5、vue2和vue3的区别
- vue3新加了Composition API 供了更灵活的生命周期钩子
- vue3加入了树摇,便于更好地按需加载
- vue2一般使用vuex进行状态管理,vue3跟更推荐pinia
- vue3对ts的支持也比vue2好
6、vue中Object.defineProperty和Proxy的区别
都是vue实现响应式数据的机制
Object.defineProperty:
是 Vue 2 中实现响应式数据的基础。它通过劫持对象的属性来实现数据的监听。
原理:
-
对对象的每个属性使用
Object.defineProperty
进行劫持。 -
通过
getter
和setter
监听属性的读取和修改。
let data = { name: 'Vue 2' };
Object.defineProperty(data, 'name', {
get() {
console.log('读取 name');
return this._name;
},
set(newValue) {
console.log('设置 name');
this._name = newValue;
}
});
data.name; // 输出:读取 name
data.name = 'Vue 3'; // 输出:设置 name
优点
-
兼容性好,支持 IE9 及以上浏览器。
-
在 Vue 2 中经过大量实践验证,稳定性高。
缺点
-
无法监听新增属性:必须预先定义属性,否则无法监听。
-
无法监听数组变化:需要额外处理数组的变异方法(如
push
、pop
等)。 -
性能问题:对嵌套对象需要递归劫持,性能开销较大。
Proxy
是 Vue 3 中实现响应式数据的基础。它通过代理整个对象来实现数据的监听。
原理
-
使用
Proxy
代理整个对象。 -
通过拦截器(handler)监听对象的读取、修改、删除等操作。
优点
-
支持监听新增属性:无需预先定义属性。
-
支持监听数组变化:无需额外处理数组的变异方法。
-
性能更好:直接代理整个对象,无需递归劫持。
-
功能更强大:可以拦截更多操作,如
deleteProperty
、has
等。
缺点
-
兼容性较差:不支持 IE 浏览器(Vue 3 已放弃对 IE 的支持)。
-
调试难度较高:由于是代理对象,调试时可能不如
Object.defineProperty
直观。
let data = { name: 'Vue 3' };
let proxy = new Proxy(data, {
get(target, key) {
console.log('读取', key);
return target[key];
},
set(target, key, value) {
console.log('设置', key);
target[key] = value;
return true;
}
});
proxy.name; // 输出:读取 name
proxy.name = 'Vue 4'; // 输出:设置 name
总结:
-
Object.defineProperty
是 Vue 2 的核心,兼容性好但功能有限。 -
Proxy
是 Vue 3 的核心,功能强大且性能更好,但兼容性较差。 -
如果你使用的是 Vue 3,
Proxy
是更好的选择;如果仍需支持 IE,可能需要考虑 Vue 2 或降级方案。
7、v-if 和 v-show
v-if是惰性的条件渲染指令,它根据给定的条件决定是否渲染元素到DOM中。当条件为真时,元素会被渲染;当条件为假时,元素不会被渲染并从DOM中移除。v-if适用于不频繁切换条件的情况,因为它会完全销毁和重建元素。
v-show是通过CSS样式的display属性来控制元素的显示和隐藏。当条件为真时,元素会显示;当条件为假时,元素会隐藏但仍保留在DOM中,只是display属性被设置为"none"。v-show适用于需要频繁切换显示状态的情况,因为它只是通过CSS修改元素的显示状态,不会重建元素,比较高效。
8、v-for 为什么要加key
这是因为 Vue 会根据 key
来追踪每个节点的身份,从而有效地进行 DOM 更新和优化渲染过程。比如说可以识别哪些项是新增的、哪些项是被删除的,哪些项只是重新排序了
9、v-for和v-if可不可以一起使用
- vue2 中 v-for 优先级大于 v-if
- vue3 中 v-if 优先级大于 v-for
最好不要一起用比如渲染列表数据时先根据 v-for
循环生成元素,再判断 v-if
是否满足条件,会造成不必要的性能消耗,提高渲染效率,还可以更精确地进行虚拟dom的比较和更新,有了 key
后,Vue 会只更新变动的元素,减少不必要的 DOM 操作。
10、vue 组件中 data 为什么必须是一个函数?
因为vue组件可以被多个页面引用,也就是说被多次实例化,而每个实例都应该具有自己的数据状态,以避免相互影响。如果data选项是一个对象时,这个数据对象在所有实例之间是共享的,这会导致意外的数据共享和状态混乱。
将data选项设置为一个函数的方式可以确保每个组件实例都返回一个新的数据对象,从而实现数据的隔离。
11、v-model的实现原理
- 初始化阶段:Vue编译器会编译HTML模板,遍历DOM元素生成虚拟DOM树。当遇到v-model属性时,会为相关节点添加input事件监听器。
- 数据绑定:通过v-bind动态绑定value属性,实现数据到视图的绑定。
- 事件监听:通过@input方法监听输入事件,当输入值变化时,更新绑定的数据属性
12、vuex与pinia的区别
Vuex 和 Pinia 都是 Vue.js 的状态管理工具,但 Pinia 是 Vuex 的现代替代方案,提供了更简洁和高效的 API。
1、API 设计
- Vuex:基于 Mutations、Actions、Getters 和 State 进行状态管理,写法较为冗长。
- Pinia:采用更直观的 API(State、Actions、Getters),不需要 Mutations,代码更简洁。
2、TypeScript 支持
- Vuex:在 Vuex 4 之前,TypeScript 支持较弱,类型推导较繁琐。
- Pinia:原生支持 TypeScript,自动推导类型,无需额外的定义。
3、Mutations
- Vuex:需要通过
mutations
来修改state
,不能直接更改state
。 - Pinia:可以直接修改
state
,不需要mutations
,更加直观。
4、模块化
- Vuex:支持模块化,但需要
namespaced: true
进行配置,使用较繁琐。 - Pinia:默认支持模块化,每个
store
就是一个模块,使用更简单。
5、组合式 API
- Vuex:主要使用选项式 API(Options API),与组合式 API 配合使用较麻烦。
- Pinia:完全兼容组合式 API(Composition API),更符合 Vue 3 设计理念。
13、computed和watch的区别
computed 和 watch 都是用于监听和响应数据变化的工具,但它们有不同的用途和工作方式
1、Computed 计算属性:
- 计算属性是基于它们依赖的数据进行计算的。你可以定义一个计算属性,该属性的值是从一个或多个数据属性派生而来的,只有当依赖的数据发生变化时,计算属性的值才会重新计算。
- 计算属性是具有缓存的。这意味着计算属性的值只会在相关依赖发生变化时重新计算,之后访问计算属性会直接返回之前缓存的值,这有助于提高性能。
- 计算属性适合用于派生数据、数据转换、数据筛选等场景。
2、Watch 监听属性:
- watch 是用于监听数据的变化,并在数据变化时执行自定义的回调函数。你可以监听一个或多个数据属性,当这些属性的值发生变化时,会触发相应的回调函数。
- watch 不具有缓存机制,每次数据变化都会触发回调函数。
- watch 适合用于需要执行异步操作、复杂逻辑、或监听多个属性变化的情况。
14、vue的插槽
Vue.js的插槽(Slot)是一种非常强大的用于在组件中定义可变的内容结构,以便父组件可以插入不同的内容。插槽允许你在组件内部定义占位符,然后在使用该组件时,将具体的内容填充到这些占位符中,从而实现更灵活的组件复用。
Vue的插槽主要分为两种类型:具名插槽和默认插槽。
1.默认插槽:当你没有给插槽一个特定的名称时,它就是默认插槽。父组件可以向默认插槽中插入内容,而这个插入的内容会显示在组件内部的默认位置。
<!-- ChildComponent.vue -->
<template>
<div>
<slot></slot>
</div>
</template>
<!-- ParentComponent.vue -->
<template>
<child-component>
<p>This content goes into the default slot.</p>
</child-component>
</template>
2.具名插槽:具名插槽允许你在组件中定义多个不同名称的插槽,以便更精确地控制内容的插入位置。
<!-- ChildComponent.vue -->
<template>
<div>
<slot name="header"></slot>
<slot name="footer"></slot>
</div>
</template>
<!-- ParentComponent.vue -->
<template>
<child-component>
<template v-slot:header>
<p>This content goes into the header slot.</p>
</template>
<template v-slot:footer>
<p>This content goes into the footer slot.</p>
</template>
</child-component>
</template>
3.作用域插槽,允许父组件向子组件传递数据,以便子组件可以在插槽内容中使用。这在实现复杂的组件交互时非常有用。
<!-- ChildComponent.vue -->
<template>
<div>
<slot :user="user"></slot>
</div>
</template>
<!-- ParentComponent.vue -->
<template>
<child-component>
<template v-slot="slotProps">
<p>{{ slotProps.user.name }}</p>
</template>
</child-component>
</template>
通过使用插槽,Vue.js组件可以更加通用和可配置,允许开发者在不同的上下文中灵活地使用和自定义组件的内容。插槽是Vue.js组件化开发中的一个强大工具,使得组件更加灵活和可重用。
15、路由模式有哪几种 什么区别
Vue的路由模式有三种:hash模式、history模式和abstract模式。
hash模式一搬用于纯静态,history用于动态路由
- Hash模式:在URL中使用#符号来表示路由,如http://example.com/#/home。在hash模式下,URL的hash值会被Vue的路由解析器解析,从而确定要渲染的组件。
- History模式:使用HTML5的History API来管理路由,URL中不再需要使用#符号,如http://example.com/home。在history模式下,URL的路径会被Vue的路由解析器解析,从而确定要渲染的组件。
- Abstract模式:在非浏览器环境中使用,如在服务器渲染时。Abstract模式不会改变URL,而是通过调用路由的API来进行路由切换。
可以通过在Vue的路由配置中设置mode属性来指定使用的路由模式,默认为hash模式。
16、你在过往开发中vue项目中 做了哪些性能优化
1、使用 Vue 的生命周期钩子:Vue 提供了一系列的生命周期钩子函数,例如 mounted、created、beforeDestroy 等。正确使用这些钩子函数可以在适当的时机执行代码,避免不必要的开销。
2、懒加载组件:延迟加载页面上的组件可以减少初始加载时的资源请求量,提高页面加载速度。Vue 提供了异步组件和路由懒加载等机制,可以实现按需加载组件的策略。
3、优化列表渲染:在使用 v-for 渲染列表时,尽可能使用 key 属性来帮助 Vue 识别每个节点的变化,从而提升更新性能。另外,避免在列表中使用复杂的计算属性或方法,尽量将计算结果缓存起来以提高渲染性能。
4、合理使用计算属性和 Watchers:使用计算属性可以缓存计算结果,避免重复计算。而使用 Watchers 可以监听数据的变化并执行相应的操作,但要避免在 Watchers 中进行复杂操作,以免影响性能。
5、路由懒加载:对于大型应用,将页面按需切分成多个异步加载的路由块可以减轻初始加载时的压力,提高首次加载速度。
6、优化图片加载:使用适当的图片格式、压缩图片大小,并使用懒加载或者按需加载的方式加载图片,可以减少页面加载时间和带宽消耗。
7、使用异步组件:将页面上的可复用组件异步加载,只在需要时才加载,可以减少初始加载时的资源负担。
8、避免不必要的重新渲染:合理使用 Vue 的响应式机制,避免频繁更新不必要的数据,从而减少重新渲染的次数。
9、使用keep-alive缓存:保持页面组件活性
10、使用Vue Devtools进行性能分析:Vue Devtools是一个浏览器插件,可以用来监控Vue应用的性能情况,包括组件的渲染时间、更新时间等,通过分析性能瓶颈,可以有针对性地进行优化。
11、合理使用v-if和v-show: v-if在条件不满足时会销毁和重建DOM元素,而v-show只是通过CSS来控制元素的显示和隐藏,所以在频繁切换显示和隐藏的情况下,使用v-show会更高效。
12、树摇:减小应用程序的文件大小,以提高加载性能。
17、vue常用的修饰符
1、事件修饰符
- .stop:阻止冒泡。
- .prevent:阻止默认事件。
- .capture:使用事件捕获模式。
- .self:只在当前元素本身触发。
- .once:只触发一次。
- .passive:默认行为将会立即触发。
2、按键修饰符
- .left:左键
- .right:右键
- .middle:滚轮
- .enter:回车
- .tab:制表键
- .delete:捕获 “删除” 和 “退格” 键
- .esc:返回
- .space:空格
- .up:上
- .down:下
- .left:左
- .right:右
- .ctrl:ctrl 键
- .alt:alt 键
- .shift:shift 键
- .meta:meta 键
3、表单修饰符
- lazy:在文本框失去焦点时才会渲染
- .number:将文本框中所输入的内容转换为number类型
- .trim:可以自动过滤输入首尾的空格
18、性能优化
- 使用
keep-alive
缓存组件,频繁切换页面可能会导致组件不断销毁和重新创建,影响性能,比如表单,列表。 - 合理使用
computed
代替methods,computed
只有当依赖的数据变化时才会重新计算,而methods
每次调用都会执行逻辑,影响性能。 - 使用
v-once
和v-memo
减少不必要的渲染
<h1 v-once>这个标题不会重新渲染</h1>
//只有 item.name 变化时才会重新渲染。vue3.2
<div v-for="item in list" :key="item.id" v-memo="[item.name]">
<p>{{ item.name }}</p>
</div>
- 避免
watch
监听深层数据,使用watch
时,如果监听的是深层对象(deep: true
),会导致 Vue 对整个对象进行深度监听,影响性能。
不推荐:
watch(
() => this.formData,
(newVal) => {
console.log(newVal);
},
{ deep: true }
);
推荐:监听具体的属性,避免监听整个对象。
watch(
() => this.formData.name,
(newVal) => {
console.log(newVal);
}
);
- 懒加载组件,Vue3 支持
defineAsyncComponent()
,在需要时才加载组件,提高首屏速度。 - Tree Shaking 可以去除未使用的代码,减少打包体积。
在 package.json 中设置 sideEffects: false,移除未使用的代码。
{
"sideEffects": false
}
19、vue如何实现爷孙组件通信
1、依赖注入provide和inject,爷爷组件使用 provide
传递数据,孙组件使用 inject
接收数据。
爷爷组件(Grandparent.vue):
<script setup>
import { provide, ref } from 'vue';
const message = ref('我是爷爷传来的数据');
provide('grandMessage', message);
</script>
<template>
<div>
<h2>爷爷组件</h2>
<Parent />
</div>
</template>
爸爸组件 (Parent.vue):
<template>
<div>
<h3>爸爸组件</h3>
<Child />
</div>
</template>
<script setup>
import Child from './Child.vue';
</script>
孙组件(Child.vue):
<script setup>
import { inject } from 'vue';
const grandMessage = inject('grandMessage');
</script>
<template>
<div>
<h4>孙组件</h4>
<p>来自爷爷的数据:{{ grandMessage }}</p>
</div>
</template>
2、 Vuex / Pinia(全局状态管理)