一、Vue和React的区别
Vue:适合快速开发、中小型项目、需要较少样板代码的场景。易上手,适合初学者。
React:适合大型复杂项目应用、需要高度灵活性的项目。适合有一定基础的开发者。
二、Vue的key的作用
给虚拟dom节点提供唯一标识。
好处:1.提高渲染性能(v-for)2.避免Vue不正确的复用(v-if input)
1.唯一性2.稳定性3.不要用数组索引
三、Vue2、Vue3的生命周期
Vue的生命周期是指一个Vue实例从创建到销毁的整个过程。
Vue2的生命周期:
1.实例创建阶段(Initialization)
· beforeCreate:常用于插件开发中执行一些初始化任务
· created:可访问data、computed、methods等/尚未挂载DOM,¥el不可用/常用于简单的Ajax请求、页面初始化操作
2.挂载阶段(DOM Mounting)
· beforeMount:很少使用,除非需要操作编译前的DOM
· mounted:可以访问渲染后的DOM/常用于需要操作DOM的库初始化、复杂的Ajax请求等
3.更新阶段(Updates):数据变化导致的虚拟DOM重新渲染和打补丁
· beforeUpdate:手动移除已添加的事件监听器
· updated:可执行以来DOM的操作(不要更改状态,可能导致无限更新循环)
4.实例销毁阶段(Destruction)
· beforeDestroy:适合清除定时器、取消订阅、解绑自定义事件等清理工作
· destroyed
5.keep-alive相关:activated/deactivated
6.errorCaptured(错误处理):捕获来自子孙组件的错误时调用。
Vue3的生命周期:
1.创建阶段
onBeforeMount:
描述:在挂载开始之前被调用,即在 setup() 返回之前。
用途:用于在组件挂载之前执行一些逻辑,例如初始化数据或进行一些准备工作。
onMounted:
描述:在组件挂载完成后被调用。
用途:用于在组件挂载后执行一些逻辑,例如获取数据或操作 DOM。
2.更新阶段
onBeforeUpdate
描述:在组件更新之前被调用。
用途:用于在组件更新之前执行一些逻辑,例如取消之前的请求或清理一些资源。
onUpdated
描述:在组件更新完成后被调用。
用途:用于在组件更新后执行一些逻辑,例如操作 DOM 或进行一些清理工作。
3.销毁阶段
onBeforeUnmount
描述:在组件卸载之前被调用。
用途:用于在组件卸载之前执行一些逻辑,例如取消订阅、清理定时器或释放资源。
onUnmounted
描述:在组件卸载完成后被调用。
用途:用于在组件卸载后执行一些逻辑,例如清理一些全局状态或日志记录。
4.错误处理
onErrorCaptured
描述:当捕获来自后代组件的错误时被调用。
用途:用于处理后代组件抛出的错误,例如记录错误或显示错误信息。
5.渲染上下文
onRenderTracked
描述:当渲染上下文中的响应式属性被追踪时被调用。
用途:用于调试和性能优化,例如记录哪些属性被追踪。
onRenderTriggered
描述:当渲染上下文中的响应式属性被触发时被调用。
用途:用于调试和性能优化,例如记录哪些属性被触发。
四、Vue3的性能为什么比Vue2更好?
1.响应式系统重构:Vue3的Proxy比Vue2的defineProperty快2-5倍,能直接检测对象属性的增删改查和数组索引变化,无需Set方法。
2.Proxy代理整个对象,避免递归遍历所有属性,初始化性能提升40%。
3.内存优化:通过WeakMap建立依赖关系,避免内存泄漏。
五、如何使用第三方库
1.是否正确安装(node_modules 中是否存在库目录)。
2.是否正确导入(路径或名称是否拼写错误)。
3.是否注册(UI 组件库需要 app.use() 或局部注册)。
4.浏览器控制台是否有报错(如依赖冲突、版本不兼容)。
六、Vue3的Proxy为什么比Vue2的defineProperty更好?
1.对象监听能力:
Vue2只能劫持已存在的属性,新增或删除属性无法自动响应。只有get0和set()。(必须使用 Vue.set() 或 this.$set() 手动添加响应式属性)
Vue3直接监听整个对象,动态增删属性也能触发响应。包括get()、set()、has()捕获in操作符、deleteProperty()、ownKeys()捕获对象属性的遍历操作。
2.数组监听能力
Vue2无法直接监听数组索引变化,必须重写数组方法(push、pop、splice等)。
Vue3原生支持数组索引修改、length变化。
3.性能优化:
Vue2初始化时递归遍历所有属性,深层嵌套对象性能较差。
Vue3惰性代理(Lazy Proxy),只有访问到的属性才会被代理,减少不必要的劫持。
4. 代码维护性
Vue 2 需要特殊处理数组和动态属性,代码逻辑复杂。
Vue 3 使用 Proxy 统一处理对象和数组,代码更简洁、可维护性更高。
优缺点比较:
1.Object.defineProperty
优点:兼容性好(支持ES5/IE9+)、可以精确控制属性(可以单独定义某个属性的行为)、适合简单场景和高频操作、访问属性不需经过代理速度更快、polyfill 友好:虽然 Object.defineProperty 本身无法 polyfill,但它的兼容性使得在旧浏览器中使用时更容易处理
缺点:无法监听新增/删除属性、无法拦截数组变化、每个属性都需单独劫持,初始化性能较慢
2.Proxy
优点:支持监听新增/删除属性,无需预先定义、支持监听数组变化(如 push、splice)、可以拦截 for…in、Object.keys、in 等操作、只需代理整个对象,而不是每个属性,初始化更快(Vue 3 改用 Proxy 提升性能)
缺点:兼容性问题(不支持IE11及以下)、无法直接代理一些特殊对象(如Date、Map、Set等)、调试稍复杂、高频操作性能较差、无法 polyfill:由于 Proxy 的实现依赖于底层的 JavaScript 引擎,无法通过 polyfill 完全模拟其行为(如拦截内部方法、原型链操作、属性描述符操作等)
七、Vue的双向绑定(v-model)
底层原理主要依赖于Vue的响应式系统和DOM更新机制。
1.编译模板:Vue会编译模板,生成渲染函数。在这个过程中,v-model会被解析成为value属性和input事件的绑定。
2.响应式数据:Vue将绑定的数据包装成响应式对象(Vue3是Proxy,Vue2是Object.defineProperty),通过定义getter和setter,Vue能监听属性值的变化。
-依赖收集:当组件渲染时,Vue通过getter收集依赖,即记录哪些组件的渲染函数依赖于特定的属性。
-发布订阅者模式:当数据发生变化时,setter会被触发,Vue会通知所有依赖于该属性的组件进行更新。
3.事件处理:Vue会为表单元素绑定input事件。用户输入时,事件处理函数会更新数据,触发响应式系统的更新。
核心原理:
1.数据劫持(Data Observation)
· 使用 Object.defineProperty()(Vue 2.x)或 Proxy(Vue 3.x)来劫持各个属性的 setter/getter
· 在数据变动时发布消息给订阅者,触发相应的监听回调
2.发布-订阅模式
· 实现了一个订阅者 Dep,用来收集订阅者 Watcher
· 数据变化时,通知所有 Watcher 对象更新视图
3.虚拟DOM与Diff算法
· 通过虚拟DOM减少直接操作真实DOM的性能开销
· 使用Diff算法高效更新DOM
Vue 使用的关键特性:
1.响应式系统
通过 reactive()(Vue 3)或 Observer(Vue 2)使数据变为响应式
自动追踪依赖,在数据变化时更新视图
2.模板编译
将模板编译成渲染函数
解析指令(如 v-model)和插值表达式
3.指令系统
v-model 指令实现双向绑定
本质上是语法糖,结合了 v-bind 和 v-on
4.Watcher 观察者
每个组件实例都有对应的 watcher 实例
在渲染过程中把属性记录为依赖,当依赖项的 setter 被调用时通知 watcher 重新计算
八、Tree-Shaking的副作用
1.误删实际使用代码
2.模块副作用评估困难
3.开发与生成环境差异
4.第三方库兼容性问题
解决方案:1.使用sideEffects属性
2.显式标记副作用
3.谨慎使用usedExports配置
4.测试生产环境构建
5.优先使用ES6模块
九、Vue的Diff算法(虚拟DOM对比算法)
Vue的Diff算法是用于比较新旧虚拟DOM树并高效更新真实DOM的核心算法:
1.同级比较:只比较同层级节点。
2.标签不同则替换:不同标签直接重建
3.标签相同则比较属性:更新变化的属性
4.比较子节点(核心部分):
· Vue2:双端交叉比较
· Vue3:双端比较+最长递增子序列优化
5.key的重要性:key帮助识别节点身份,减少不必要的DOM操作。
为什么需要虚拟DOM和Diff:
1.直接操作DOM代价高昂
2.批量更新:通过虚拟DOM收集多次变化后一次性更新
3.跨平台:虚拟DOM抽象乐渲染过程
4.性能优化:通过Diff找出最小变更集
最佳实践:
1.合理使用key
2.避免动态子节点层级变化
3.大列表使用虚拟滚动
4.合理拆分组件
十、Vue的Vuex状态管理
Vuex是Vue.js的官方状态管理库,主要用于管理Vue应用中的全局状态。他的核心概念是通过集中式存储来管理应用的所有组件的状态,确保每个组件能共享和访问相同的数据,并且状态的变化是可预测和可追踪的。
State(状态)
Vue的state是单一状态树(即一个对象),存储整个应用的状态,所有组件都可以从state中读取数据,用于共享。
mutations(变更)
mutation是唯一能够修改state的方法,是同步操作【否则无法保证状态何时被修改,无法准确通知其他组件进行视图更新】,接受两个参数:当前的状态对象和传入的参数(payload)
actions(动作)
和mutations类似,但是用于处理异步操作。不能直接修改而是通过提交mutations修改。
getters(计算属性)
类似Vue组件中的计算属性(computed)。可以对state中的数据进行派生计算或转换。
modules(模块化)
应用变得越来越大时,可以将Vuex的store拆分成模块(moudles),每个模块有自己的state、mutations、actions和getters,模块化有卒于管理大型应用的复杂性。
src/
└── store/
├── index.js # 主入口文件,在这里定义state
├── actions.js # 存放actions
├── mutations.js # 存放mutations
└── getters.js # 存放getters
典型state定义示例(store/index.js)
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
// state 定义在这里
state: {
count: 0,
user: null,
todos: []
},
mutations: { /*...*/ },
actions: { /*...*/ },
getters: { /*...*/ }
})
1.集中式状态管理:所有组件共享一个全局的state,方便管理
2.组件共享数据:跨组件共享状态,避免 prop 层层传递
3.响应式数据:Vuex 的 state 是响应式的,数据变化会自动更新视图
4.可预测的状态变化:通过强制使用 mutations 严格管理状态变更,确保可追踪
十一、Vue Router的编程式导航
// 基本跳转
this.$router.push('/about')
// 带参数跳转
this.$router.push({ path: '/user', query: { id: '123' } })
// 命名路由跳转
this.$router.push({ name: 'User', params: { userId: '123' } })
// 替换当前路由(不记录历史)
this.$router.replace('/login')
// 前进/后退
this.$router.go(1) // 前进一步
this.$router.go(-1) // 后退一步

十二、防抖和节流
防抖:
function debounce(func, wait){
let timeout;
return function(){
const context=this;
const args = arguments;
clearTimeout(timeout);
timeout = setTimeout(function(){
func.apply(context,args);
},wait)
}
}
const handleInput = debounce((event)=>{
console.log(event.target.value)
},300)
document.querySelector('input').addEventListener('input',handleInput);
节流:
function throttle(func, wait){
let timeout=null;
return function(){
const args=arguments;
const context=this;
if(!timeout){
timeout=setTimeout(function(){
func.apply(context,args);
},wait)
}
}
}
十三、v-if的优先级和v-for的优先级哪个更高?同时出现怎么优化?v-show和v-if的区别?
v-for更高。建议将v-if应用到包裹的父元素上、使用v-show替代v-if,或者使用计算属性来过滤需要渲染的数据项(在v-for前先筛选)。
v-show通过CSS的display属性来控制元素的显示和隐藏。(当条件为true时,元素的display属性设置为block(或原始值)。当条件为false时,元素的display属性设置为none。)元素始终存在于DOM中,只是样式被修改。性能较好,适合条件经常变化的场景。
v-if通过DOM的创建和销毁控制显示和隐藏,性能较差,适合条件不经常变化的场景。
十四、Vuex和pinia的区别
Pinia是Vuex的替代方案,API更简单更接近组合式API,不需要mutations,可以直接在actions中修改state(可同步也可异步)。提供了更灵活的方式来定义状态和逻辑,没有Vuex的严格性。
vuex
// store.js
import { createStore } from 'vuex'
export default createStore({
state: { count: 0 },
mutations: {
increment(state) { state.count++ }
},
actions: {
increment({ commit }) { commit('increment') }
},
getters: {
doubleCount: state => state.count * 2
}
})
pinia
// stores/counter.js
import { defineStore } from 'pinia'
export const useCounterStore = defineStore('counter', {
state: () => ({ count: 0 }),
actions: {
increment() { this.count++ }
},
getters: {
doubleCount: (state) => state.count * 2
}
})
十五、路由鉴权
首先在路由的meta字段添加一个属性用来表示该路由是否需要鉴权。
const routes=[{
path:'/dashboard',
component:Dashboard,
meta:{requiresAuth:true}
}]
2.利用Vue Router提供的路由守卫函数,如beforeEach、beforeResolve和afterEach等。
router.beforeEach((to,from,next)=>{
const token = useUserStore().userInfo.token
if(to.meta.requiresAuth && !token){
next('/login')//没有token,重定向到登录页
}else{
next()//否则允许访问
}
})
十六、解释Vue Router中的导航守卫(navigation guards)
导航守卫的执行顺序
1.导航被触发
2.调用失活组件的 beforeRouteLeave 守卫
3.调用全局的 beforeEach 守卫
4.在重用的组件里调用 beforeRouteUpdate 守卫
5.调用路由配置里的 beforeEnter 守卫
6.解析异步路由组件
7.在被激活的组件里调用 beforeRouteEnter 守卫
8.调用全局的 beforeResolve 守卫
9.导航被确认
10.调用全局的 afterEach 钩子
11.触发 DOM 更新
12.调用 beforeRouteEnter 守卫中传给 next 的回调函数,创建好的组件实例会作为回调函数的参数传入
全局守卫(作用于所有路由的守卫)
1.beforeEach - 全局前置守卫
router.beforeEach((to, from, next) => {
// 在路由跳转前执行
next() // 必须调用 next() 继续导航
})
2.beforeResolve - 全局解析守卫
router.beforeResolve((to, from, next) => {
// 在所有组件内守卫和异步路由组件被解析后调用
next()
})
3.afterEach - 全局后置钩子
router.afterEach((to, from) => {
// 导航完成后执行,不需要调用 next()
})
路由独享守卫(在路由配置中直接定义的守卫)
const router = new VueRouter({
routes: [
{
path: '/admin',
component: Admin,
beforeEnter: (to, from, next) => {
// 只对该路由生效
if (!isAdmin()) next('/login')
else next()
}
}
]
})
组件内守卫(在组件内部定义的守卫)
1.beforeRouteEnter - 进入组件前
beforeRouteEnter(to, from, next) {
// 不能访问 this,因为组件实例还未创建
next(vm => {
// 通过 vm 访问组件实例
})
}
2.beforeRouteUpdate - 当前路由改变但组件被复用时
beforeRouteUpdate(to, from, next) {
// 可以访问 this
this.name = to.params.name
next()
}
3.beforeRouteLeave - 离开组件前
beforeRouteLeave(to, from, next) {
// 可以访问 this
const answer = confirm('确定要离开吗?未保存的更改将会丢失')
if (answer) next()
else next(false)
}
十六、watch和watchEffect的区别
watch
1.watch显式声明监听的目标(如ref、reractive对象或计算属性)。
2.默认仅在依赖时触发回调。(立即执行需传入{immediate:true})
3.回调函数中可获取旧值和新值。
4.精确控制监听目标,适合需要旧值或惰性执行的场景。
import { ref, watch } from 'vue';
const count = ref(0);
// 监听单个 ref
watch(count, (newValue, oldValue) => {
console.log(`count 从 ${oldValue} 变为 ${newValue}`);
});
// 监听多个源(数组形式)
watch([count, anotherRef], ([newCount, newAnother], [oldCount, oldAnother]) => {
// 处理变化...
});
watchEffect
1.watchEffect自动追踪回调函数内使用的所有响应式依赖。
2.立即执行一次,并在依赖变化时重新执行。
3.无法获取旧值,仅能访问当前值。
4.自动依赖追踪,适合即时反应或副作用逻辑。
import { ref, watchEffect } from 'vue';
const count = ref(0);
// 自动追踪 count 的变化
watchEffect(() => {
console.log(`count 的值是 ${count.value}`); // 自动依赖收集
});
十七、SASS是什么?
Sass 是一种 CSS 预处理器,扩展了 CSS 的功能,支持:
1.变量:存储颜色、字体等重复使用的值。
2.嵌套规则:简化选择器的层级结构。
3.混合(Mixins):复用样式代码块。
4.函数和运算:动态计算样式值。
5.模块化:通过 @use 或 @import 拆分样式文件。
变量
$theme-colors: (
primary: #42b983,
danger: #ff4757
);
.button {
background-color: map-get($theme-colors, primary);
}
嵌套
.nav {
ul {
margin: 0;
li {
padding: 0.5rem;
a {
color: blue;
}
}
}
}
混合(Mixins)
// mixins.scss
@mixin flex-center($direction: row) {
display: flex;
justify-content: center;
align-items: center;
flex-direction: $direction;
}
// 使用
.container {
@include flex-center(column);
}
函数
@function px-to-rem($px) {
@return ($px / 16px) * 1rem;
}
.text {
font-size: px-to-rem(24px); // 转换为 1.5rem
}
十八、Vue实现组件间的样式隔离
1.Scoped CSS(最常用)
原理:通过 scoped 属性自动为组件样式添加唯一属性选择器(如 data-v-f3f3eg9)。
适用场景:绝大多数 Vue 单文件组件。
<template>
<div class="my-component">仅本组件生效</div>
</template>
<style scoped>
/* 编译后变为 .my-component[data-v-f3f3eg9] */
.my-component {
color: red;
}
</style>
特点:
✅ 自动隔离,无需额外配置
❌ 无法直接影响子组件样式(需用深度选择器)
❌ 生成的哈希属性可能影响CSS优先级
深度选择器(影响子组件样式):
/* Vue 2 */
.parent >>> .child { color: blue; }
.parent /deep/ .child { color: blue; }
/* Vue 3 */
.parent ::v-deep(.child) { color: blue; }
2.CSS Modules(工程化推荐)
原理:将类名编译为哈希字符串,实现真正隔离。
适用场景:需要严格隔离的中大型项目。
<template>
<div :class="$style.myModuleClass">模块化样式</div>
</template>
<style module>
.myModuleClass {
background: green;
}
/* 编译后变为 ._3zyde4l1 */
</style>
配置(在 vue.config.js 中全局启用):
module.exports = {
css: {
modules: true, // 默认对所有样式文件启用 CSS Modules
localsConvention: 'camelCase' // 类名转为驼峰
}
}
特点:
✅ 完全隔离,类名动态生成
✅ 支持 JavaScript 动态类名
❌ 类名无法预测,调试稍复杂
3.Shadow DOM(原生隔离方案)
原理:利用 Web 标准的 Shadow DOM 实现样式沙箱。
适用场景:需要完全隔离的微前端或独立组件库。
<template>
<div ref="host"></div>
</template>
<script>
export default {
mounted() {
const shadowRoot = this.$refs.host.attachShadow({ mode: 'open' });
shadowRoot.innerHTML = `
<style>
div { color: purple; } /* 仅在此 Shadow DOM 内生效 */
</style>
<div>Shadow DOM 内容</div>
`;
}
};
</script>
特点:
✅ 浏览器原生支持,绝对隔离
❌ 无法直接使用全局样式
❌ 兼容性问题(IE 不支持)
十九、keep-alive是什么?
1.keep-alive 的核心原理:是通过缓存组件的实例来避免销毁和重新创建。它会将缓存的组件实例存储在内存中,并在需要时复用这些实例。
-当组件被激活时,Vue 会检查该组件是否已经被缓存。如果是,则直接复用缓存的实例;如果不是,则创建新实例并缓存。
-当组件被停用时,Vue 不会销毁它,而是将其缓存起来。
2.keep-alive的生命周期钩子:
-activated:当组件被激活时触发。
-deactivated:当组件被停用时触发。
<template>
<keep-alive>
<component :is="currentView"></component>
</keep-alive>
</template>
<script>
export default {
data() {
return {
currentView: 'ComponentA'
};
}
};
// ComponentA.vue
export default {
activated() {
console.log('组件被激活');
},
deactivated() {
console.log('组件被停用');
}
};
</script>
3.keep-alive的属性:用于控制缓存行为
include:指定需要缓存的组件名称或正则表达式。
exclude:指定不需要缓存的组件名称或正则表达式。
max:设置缓存的最大数量。
<keep-alive include="ComponentA,ComponentB" max="2">
<component :is="currentView"></component>
</keep-alive>
在这个例子中:
只会缓存 ComponentA 和 ComponentB。
缓存的最大数量为 2,超出时会移除最早的缓存。
4.keep-alive的使用场景:
需要保留组件状态(如表单输入、滚动位置等)。
频繁切换的组件(如标签页、路由页面等)。
需要减少渲染开销的场景。
5.注意事项:
keep-alive 会缓存组件的实例,如果缓存的组件过多,可能会占用大量内存,影响性能。
对于不需要缓存的组件,可以通过 exclude 属性排除。
在某些情况下,可能需要手动清除缓存,例如使用 v-if 控制 keep-alive 的显示。
二十、Vue中带有$的属性和方法
属性
1.$data:Vue 实例的数据对象,包含实例中定义的数据。
const app = new Vue({
data: {
message: 'Hello, Vue!'
}
});
console.log(app.$data.message); // 输出:Hello, Vue!
2.$props:包含了父组件传递给子组件的属性。
Vue.component('my-component', {
props: ['message'],
mounted() {
console.log(this.$props.message); // 输出:Hello, Vue!
}
});
3.$el:Vue 实例关联的根 DOM 元素。
const app = new Vue({
el: '#app'
});
console.log(app.$el); // 输出:#app 元素的 DOM 引用
4.$options:用于当前 Vue 实例的初始化选项。
const app = new Vue({
data: {
message: 'Hello, Vue!'
},
created() {
console.log(this.$options.data().message); // 输出:Hello, Vue!
}
});
5.$refs:包含了组件中所有拥有 ref 特性的 DOM 元素或子组件实例。
const app = new Vue({
data: {
message: 'Hello, Vue!'
},
created() {
console.log(this.$options.data().message); // 输出:Hello, Vue!
}
});
6.$emit():在子组件中触发父组件的事件。
<div id="app">
<my-component @custom-event="handleCustomEvent"></my-component>
</div>
<script>
Vue.component('my-component', {
methods: {
handleClick() {
this.$emit('custom-event', 'Hello from child component!');
}
}
});
const app = new Vue({
methods: {
handleCustomEvent(data) {
console.log(data); // 输出:Hello from child component!
}
}
});
</script>
7.$on():监听当前实例上的自定义事件。
const app = new Vue({
created() {
this.$on('custom-event', data => {
console.log(data); // 输出:Hello from child component!
});
}
});
8.$nextTick():在下次 DOM 更新循环结束之后执行延迟回调。
const app = new Vue({
data: {
message: 'Hello, Vue!'
},
methods: {
updateMessage() {
this.message = 'Updated message';
this.$nextTick(() => {
console.log(this.$el.textContent); // 输出:Updated message
});
}
}
});
9.$watch():用于观察 Vue 实例上的数据变化。
const app = new Vue({
data: {
message: 'Hello, Vue!'
},
watch: {
message(newMessage, oldMessage) {
console.log(`Old message: ${oldMessage}, New message: ${newMessage}`);
}
}
});
10.$router:Vue Router 实例,用于在 Vue 应用中进行路由导航。
const router = new VueRouter({
routes: [
{ path: '/', component: Home },
{ path: '/about', component: About }
]
});
const app = new Vue({
router,
methods: {
goToAboutPage() {
this.$router.push('/about'); // 导航到 /about 页面
}
}
});
11.$attrs:用于访问父组件传递给子组件但子组件没有显式声明接收的非 prop 特性(attribute)集合。
<div id="app">
<my-component title="Custom Title"></my-component>
</div>
<script>
Vue.component('my-component', {
mounted() {
console.log(this.$attrs.title); // 输出:Custom Title
}
});
const app = new Vue();
</script>
方法
1.$set(object, key, value):在 Vue 实例或组件实例中设置响应式属性的方法。
const vm = new Vue({
data: {
obj: {}
}
});
vm.$set(vm.obj, 'newKey', 'newValue');
2.$delete(object, key):在 Vue 实例或组件实例中删除属性的方法。
const vm = new Vue({
data: {
obj: { key: 'value' }
}
});
vm.$delete(vm.obj, 'key');
3.$forceUpdate():强制当前 Vue 实例重新渲染。
const vm = new Vue({
data: {
message: 'Hello'
},
methods: {
updateMessage() {
this.message = 'Updated';
this.$forceUpdate();
}
}
});
4.$createElement():用于创建虚拟 DOM 元素的方法,通常在渲染函数中使用。
const MyComponent = {
render(createElement) {
return createElement('div', 'Hello, Vue!');
}
};
5.$destroy():销毁当前 Vue 实例。
const vm = new Vue({
el: '#app'
});
vm.$destroy();
6.$refs:引用子组件或 DOM 元素的特殊属性。
<div id="app">
<my-component ref="myRef"></my-component>
</div>
<script>
const app = new Vue({
mounted() {
console.log(this.$refs.myRef); // 输出:子组件的实例或 DOM 元素的引用
}
});
</script>
7.$slots:访问插槽内容的属性,用于处理父组件传递的内容。
<my-component>
<span slot="header">Header</span>
<span slot="footer">Footer</span>
</my-component>
8.$scopedSlots:访问具名插槽内容的属性,用于处理父组件传递的具名插槽内容。
<my-component>
<template v-slot:default="slotProps">
<span>{{ slotProps.text }}</span>
</template>
</my-component>
二十一、虚拟DOM的底层原理(Diff策略)
1.虚拟DOM的基本结构
虚拟 DOM 是一个轻量级的 JavaScript 对象,用于描述真实 DOM 的结构和属性。例如,一个简单的 HTML 元素:
<div id="app" class="container">
<p>Hello, Virtual DOM!</p>
</div>
对应的虚拟 DOM 可能是这样的:
const vNode = {
tag: 'div',
props: {
id: 'app',
className: 'container'
},
children: [
{
tag: 'p',
props: {},
children: ['Hello, Virtual DOM!']
}
]
}
2.虚拟DOM的工作流程
(1)生成虚拟DOM
当组件的状态(state 或 props)发生变化时,框架会重新调用 render() 方法生成新的虚拟 DOM 树:
// React 示例
function App() {
const [count, setCount] = useState(0);
return (
<div>
<button onClick={() => setCount(count + 1)}>Click</button>
<p>Count: {count}</p>
</div>
);
}
每次 count 变化时,React 会重新生成一棵新的虚拟 DOM 树。
(2)Diff 算法(对比新旧虚拟 DOM)
框架不会直接替换整个 DOM,而是通过 Diff 算法 找出新旧虚拟 DOM 之间的差异(最小变更集)。常见的 Diff 策略:
1.同级比较(Tree Diff)
只比较同一层级的节点,不跨层级比较(时间复杂度 O(n))。
2.Key 优化(List Diff)
给列表元素添加 key,帮助框架识别哪些节点可以复用(避免不必要的重新渲染)。
3.组件级优化
如果组件类型不同(如从 变成 ),直接销毁旧组件,创建新组件。
(3)批量更新真实 DOM(Patch)
Diff 完成后,框架会生成一个 补丁(Patch),并批量更新真实 DOM:
// 伪代码
function patch(oldNode, newNode) {
if (newNode.text !== oldNode.text) {
oldNode.textContent = newNode.text; // 更新文本
}
if (newNode.children.length > oldNode.children.length) {
oldNode.appendChild(createElement(newNode.children[1])); // 新增节点
}
}
关键优化:
批量更新:减少 DOM 操作次数(避免频繁重排/重绘)。
异步渲染:React 的 Fiber 架构允许中断和恢复渲染,避免阻塞主线程。
二十二、虚拟DOM和真实DOM的比较
虚拟DOM定义:一个轻量级的Javascript对象,是真实DOM的内存表示。通过对比和批量更新来优化性能。
优点:
1.性能优化
·批量更新:将多次DOM操作合并为一次,减少重排和重绘。
·Diff算法:通过对比新旧虚拟DOM树,仅更新变化的部分(最小化DOM操作)。
2.开发体验好
·开发者无需手动优化DOM操作(如React的setState自动批处理)。
·声明式编程(只需关心状态,而非具体DOM操作)。
3.跨平台能力
虚拟DOM是JavaScript对象,可渲染到不同平台(如React Native、SSR)。
4.更好的可测试性
虚拟DOM可以在Node.js环境中测试,无需浏览器。
缺点:
1.内存开销
需要维护虚拟DOM的副本,内存占用略高(但对现代设备影响极小)。
2.首次渲染稍慢
初次渲染需额外步骤:生成虚拟DOM → Diff → 更新真实DOM。
3.不适用于极高频更新
在动画或高频交互场景(如游戏),直接操作DOM可能更高效。
真实DOM定义:浏览器中实际存在的DOM树,直接操作真实DOM会触发浏览器的重排(Reflow)和重绘(Repaint)。
优点:
1.直接有效:直接操作真实DOM,无需额外的抽象层,适合简单场景或小型应用。
2.无虚拟DOM开销:省去了虚拟DOM的创建和对比过程,在极少量DOM操作时可能更快。
缺点:
1.性能瓶颈:直接操作真实DOM会触发浏览器的重排和重绘,频繁操作(如循环更新)会导致性能下降。
2.开发效率低:需要手动优化DOM操作(如批量更新),代码维护成本高。
3.跨平台限制:依赖浏览器环境,难以直接用于非Web平台(如移动端、服务端渲染)。
二十三、Vue路由懒加载
路由懒加载是一种优化前端应用性能的技术,通过按需加载资源,减少应用初始加载时间,提升用户体验。
const Foo = () => import('./Foo.vue')
const Bar = () => import('./Bar.vue')
const routes = [
{ path: '/foo', component: Foo },
{ path: '/bar', component: Bar }
]
二十四、解决回调地狱的 Promise 方案
回调地狱(Callback Hell)是指多层嵌套的回调函数导致的代码难以阅读和维护的问题。Promise 是解决回调地狱的主要方案之一。
1.回调地狱示例
doSomething(function(result) {
doSomethingElse(result, function(newResult) {
doThirdThing(newResult, function(finalResult) {
console.log('最终结果: ' + finalResult);
}, failureCallback);
}, failureCallback);
}, failureCallback);
2.Promise解决方案
doSomething()
.then(result => doSomethingElse(result))
.then(newResult => doThirdThing(newResult))
.then(finalResult => {
console.log('最终结果: ' + finalResult);
})
.catch(failureCallback);
特点:
链式调用:每个 then() 返回一个新的 Promise
值传递:前一个 then() 的返回值会传递给下一个 then()
错误冒泡:链中任何地方出错都会跳到最近的 catch()
3.高级用法:Promise.all、Promise.race、Promise.allSettled
4.实际应用实例
用户登录流程:
login(userCredentials)
.then(userData => getUserProfile(userData.id))
.then(profile => getNotifications(profile.userId))
.then(notifications => {
console.log('登录成功,通知:', notifications);
return updateLastLogin();
})
.then(() => {
console.log('所有操作完成');
})
.catch(error => {
console.error('登录流程出错:', error);
});
文件处理流程
readFile('input.txt')
.then(content => processContent(content))
.then(processed => writeFile('output.txt', processed))
.then(() => console.log('文件处理完成'))
.catch(err => console.error('处理失败:', err));
5.进一步优化 - 使用async/await
虽然 Promise 已经解决了回调地狱,但 ES2017 的 async/await 语法可以进一步简化代码:
async function handleUserLogin() {
try {
const userData = await login(userCredentials);
const profile = await getUserProfile(userData.id);
const notifications = await getNotifications(profile.userId);
console.log('登录成功,通知:', notifications);
await updateLastLogin();
console.log('所有操作完成');
} catch (error) {
console.error('登录流程出错:', error);
}
}
二十五、解释Vue中的computed,什么时候用watch,什么时候用computed?
定义:computed属性是带有缓存功能的计算属性。它的值依赖于其他响应式数据,当这些依赖项变化时,computed属性会重新计算。
基本用法:Vue3中可以用reactive和ref结合computed来定义。
import {reactive,ref,computed} from 'vue';
const state=reactive({
count:1
});
const doubleCount=computed(()=>state.count*2);
内部机制:缓存机制、Lazy Evaluation惰性求值,访问时才计算
特性:1.自动依赖追踪2.简洁的语法3.支持getter和setter
使用 computed:
当你需要基于响应式数据计算一个派生值时。
当你需要避免重复计算时。(缓存机制)
当你需要保持数据的一致性和可预测性时。(computed属性默认只读)
使用 watch:
当你需要在响应式数据变化时执行副作用时。(监听响应式数据)
当你需要监听多个数据源的变化时。
当你需要执行异步操作时。
二十六、Vue 弹窗组件实现与使用
弹窗组件实现
首先创建一个 Modal.vue 文件:
<template>
<Transition name="modal">
<div v-if="modelValue" class="modal-overlay" @click.self="close">
<div class="modal-container">
<div class="modal-header">
<h3>{{ title }}</h3>
<button class="close-button" @click="close">×</button>
</div>
<div class="modal-body">
<slot></slot>
</div>
<div class="modal-footer" v-if="$slots.footer">
<slot name="footer"></slot>
</div>
</div>
</div>
</Transition>
</template>
<script setup>
import { watch } from 'vue';
const props = defineProps({
modelValue: {
type: Boolean,
default: false
},
title: {
type: String,
default: '提示'
}
});
const emit = defineEmits(['update:modelValue', 'close']);
const close = () => {
emit('update:modelValue', false);
emit('close');
};
watch(() => props.modelValue, (newVal) => {
if (newVal) {
document.body.style.overflow = 'hidden';
} else {
document.body.style.overflow = '';
}
});
</script>
<style scoped>
.modal-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.5);
display: flex;
justify-content: center;
align-items: center;
z-index: 1000;
}
.modal-container {
background: white;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.33);
width: 80%;
max-width: 500px;
max-height: 80vh;
display: flex;
flex-direction: column;
overflow: hidden;
}
.modal-header {
padding: 16px;
display: flex;
justify-content: space-between;
align-items: center;
border-bottom: 1px solid #eee;
}
.modal-body {
padding: 16px;
overflow-y: auto;
flex-grow: 1;
}
.modal-footer {
padding: 16px;
border-top: 1px solid #eee;
display: flex;
justify-content: flex-end;
gap: 8px;
}
.close-button {
background: none;
border: none;
font-size: 24px;
cursor: pointer;
color: #666;
}
.close-button:hover {
color: #333;
}
/* 过渡动画 */
.modal-enter-from,
.modal-leave-to {
opacity: 0;
}
.modal-enter-active,
.modal-leave-active {
transition: opacity 0.3s ease;
}
.modal-enter-to,
.modal-leave-from {
opacity: 1;
}
</style>
全局注册组件
如果你想全局注册这个组件,可以在 main.js 中:
import { createApp } from 'vue';
import App from './App.vue';
import Modal from './components/Modal.vue';
const app = createApp(App);
app.component('Modal', Modal);
app.mount('#app');
局部引用组件
<template>
<div>
<button @click="showModal = true">打开弹窗</button>
<Modal
v-model="showModal"
title="自定义标题"
@close="handleClose"
>
<p>这里是弹窗内容</p>
<template #footer>
<button @click="showModal = false">取消</button>
<button @click="confirmAction">确认</button>
</template>
</Modal>
</div>
</template>
<script setup>
import { ref } from 'vue';
import Modal from '@/components/Modal.vue';
const showModal = ref(false);
const handleClose = () => {
console.log('弹窗已关闭');
};
const confirmAction = () => {
console.log('执行确认操作');
showModal.value = false;
};
</script>
二十七、vue2和vue3diff算法的区别
Vue 3 和 Vue 2 在 Diff 算法上有显著的区别,主要体现在以下几个方面:
- 节点对比策略
Vue 2:采用双端 Diff 算法,通过头尾指针双向遍历,尝试复用节点。即使某些节点没有变化,仍然会重新比对所有子节点,导致额外的计算开销。
Vue 3:引入了静态标记和动态追踪。在编译阶段,Vue 3 会通过 PatchFlag 标记动态节点(如 class、style、props 等),Diff 时直接跳过静态节点,仅对比动态变化的节点,减少不必要的遍历。 - 多根节点支持
Vue 2:每个组件必须有一个根节点,多个兄弟节点需要包裹在一个父节点中,这导致了额外的 DOM 层级。
Vue 3:支持Fragment,允许组件返回多个根节点,减少不必要的 DOM 结构,提高渲染效率。 - 列表 Diff 优化
Vue 2:采用双端对比,效率较低,特别是在节点顺序变化较大时。
Vue 3:采用**最长递增子序列(LIS)**优化,尽可能复用已有节点,避免不必要的 DOM 操作,提高列表更新的效率。 - 事件处理
Vue 2:每次更新会重新生成事件函数,导致不必要的 Diff 对比。
Vue 3:对静态事件(如 @click=“handleClick”)进行缓存,复用之前的事件函数,避免重复对比。 - 编译时优化
Vue 2:没有编译时优化,所有节点都会参与 Diff。
Vue 3:引入了Block Tree和PatchFlag。Block Tree 将动态节点划分为“块”,Diff 时以块为单位更新,减少遍历量。 - 性能对比
假设一个组件包含 1000 个静态节点和 1 个动态节点:
Vue 2:需要遍历 1001 个节点。
Vue 3:通过 PatchFlag 跳过静态节点,仅对比 1 个动态节点。
总结
Vue 3 的 Diff 算法相比 Vue 2 更加高效,主要优化点包括:
减少不必要的节点遍历:通过 PatchFlag 和 Block Tree,仅对动态节点进行 Diff。
支持多根节点:通过 Fragment 减少不必要的 DOM 层级。
优化列表 Diff:采用 LIS 算法,减少 DOM 操作。
二十八、Vue3和Vue2的区别?
1.Composition API:Vue 3引入了Composition API,这是其核心新特性之一。它改变了Vue组件的代码结构,使得逻辑组织更加灵活和清晰。相比Vue 2中的Options API,Composition API使得状态管理、生命周期函数和逻辑复用变得更加简洁。
2.更好的TypeScript支持:Vue 3在TypeScript支持方面有了显著的提升。Vue 2的TypeScript支持比较基础,而Vue 3对TypeScript的集成更加紧密,使得开发者能够更轻松地进行类型推导和错误检查。
3.性能提升:Vue 3在性能方面进行了大量优化。由于Vue 3重写了虚拟DOM和依赖追踪机制,其渲染性能比Vue 2提高了近40%。此外,Vue 3采用了更加高效的编译策略,使得应用的启动速度和渲染速度都得到了显著提升。
4.更小的打包体积:Vue 3的打包体积相比Vue 2更小。Vue 3的编译器使用了更高效的代码分割和Tree Shaking技术,使得最终打包的文件体积更加紧凑,从而减少了客户端的加载时间。
5.新增的生命周期钩子:Vue 3引入了新的生命周期钩子函数,如onMounted、onUpdated和onUnmounted,这些钩子函数是为Composition API设计的,使得开发者可以更加灵活地管理组件的生命周期
6.响应式系统改进
二十九、Vue的单向绑定(v-bind)
定义
单向绑定 是一种更严格的数据绑定方式,它只允许数据从数据层流向视图层,而视图层的变化需要通过事件处理来手动更新数据层。Vue.js 使用 v-bind 和事件监听来实现单向绑定。
特点
数据流清晰:数据流是单向的,从数据层到视图层,视图层的变化通过事件触发更新。
可维护性高:在复杂的应用中,单向绑定可以避免数据同步的混乱,提高代码的可维护性。
性能优化:在某些情况下,单向绑定可以减少不必要的数据更新,提高性能。
表单设计:单向绑定 vs 双向绑定
使用场景
简单表单:如果表单字段较少,且逻辑简单,使用 双向绑定 可以更快速地实现功能,代码更简洁。
复杂表单:如果表单字段较多,且需要更复杂的逻辑处理(如输入验证、动态字段、表单重置等),使用 单向绑定 可以更好地控制数据流,提高代码的可维护性和性能。
三十、Vue组件之间如何进行通信
1.父子组件通信
父 → 子:Props
// 父组件
<ChildComponent :message="parentMessage" />
// 子组件
props: {
message: String
}
子 → 父:自定义事件
// 子组件
this.$emit('update', newValue);
// 父组件
<ChildComponent @update="handleUpdate" />
2.非父子组件通信
事件总线(Event Bus)
// 创建事件总线(通常在单独的模块中)
export const eventBus = new Vue();
// 发送事件
eventBus.$emit('event-name', data);
// 接收事件
eventBus.$on('event-name', (data) => {
// 处理数据
});
Vuex 状态管理
适合中大型应用,集中式状态管理
// store.js
const store = new Vuex.Store({
state: { count: 0 },
mutations: {
increment(state) {
state.count++
}
}
});
// 组件中使用
this.$store.commit('increment');
3.其他通信方式
provide/inject(祖先 → 后代)
// 祖先组件
provide() {
return {
theme: this.themeData
}
}
// 后代组件
inject: ['theme']
$parent / $children(直接访问)
// 访问父组件
this.$parent.someMethod();
// 访问子组件
this.$children[0].someMethod();
$refs
// 父组件
<ChildComponent ref="child" />
// 访问子组件
this.$refs.child.someMethod();
本地存储
// 存储
localStorage.setItem('key', JSON.stringify(data));
// 获取
const data = JSON.parse(localStorage.getItem('key'));
选择建议
简单父子通信:props 和自定义事件
兄弟组件或远亲组件:事件总线或 Vuex
大型应用:Vuex
避免直接修改父组件状态:始终使用事件通知父组件

被折叠的 条评论
为什么被折叠?



