1 说说你对vue的理解:
# 1 说说你对vue的理解:
一个用于创建用户界面的开源的js框架,也是一个创建单页应用的web应用框架
是一套构建用户界面的渐进式MVVM框架。啥是渐进式呢?强制主张最少
只关心视图渲染,然后由于渐进式的特性vue便于与第三方库整合
你对渐进式框架的理解:
没有多做职责之外的事情,只做了自己该做的事,没有做不该做的事;(vue不规定你一次性接受并使用它的全部功能特性)
在场景中vue也发挥了很大的作用:不需要复杂的特性就能上手‘随着规模的增大可以逐渐引入路由、状态集中管理等功能
# 2 Vue有了数据响应式,为何还要diff?
明显两个不同的概念
数据响应式是数据和视图同步更新
虚拟DOM和diff算法是提高页面渲染的效率和性能,减少了不必要的
# 3 vue3为什么不需要时间分片
性能已经很优了,不需要再利用时间分片等技术来提升了
# 4 vue3为什么要引入CompositionAPI
其实就是说vue3的优点: 代码组织
逻辑复用
ts支持
解决vue2的一些问题
# 5 vue事件机制,事件循环相关的
事件机制
在vue里,事件处理分两种情况
原生DOM事件
<button @click="handleClick">点我</button>Vue 底层其实是调用了
addEventListener给元素注册事件。事件发生时,会触发
handleClick方法。这部分就是标准的浏览器事件模型(捕获/冒泡)。
组件自定义事件
<!-- 父组件 --> <MyButton @custom-click="doSomething" /> <!-- 子组件 --> <button @click="$emit('custom-click')">点我</button>- 子组件通过
$emit触发事件 - 父组件通过
@xxx监听事件。 - 这是 Vue 内部实现的一个 事件派发与监听机制,本质上就是一个 发布-订阅模式。
- 子组件通过
事件机制
Vue 的事件机制离不开 JavaScript 的运行机制,所以要看 Event Loop。
Vue 的 DOM 更新机制 就和 Event Loop 结合得很紧密:
- 当你修改响应式数据时,Vue 不会立刻更新 DOM,而是把更新任务推入 异步队列。
- Vue 内部用的是 微任务(Promise.then) 或者 宏任务(setTimeout) 来实现“批量更新”。
- 所以你修改多次数据,Vue 会在 同一轮事件循环里合并更新,最终只触发一次 DOM 渲染。
# 6 怎么在vue中定义全局方法?
全局api:
- vue2用原型 vue.prototype
- vue3用app.config.globalProperties
混入
创建插件
# 7 vue中父组件怎么监听到子组件的生命周期
- 子组件事件通信emit,适合单向数据流
- 使用ref拿到子组件实例,等子组件挂载好主动调用方法
# 8 为什么react需要fiber架构,而vue却不需要
fiber架构使得react实现异步渲染和任务优先级变得更加容易
然后vue的话也不是不需要是采取另外一种响应式系统和渲染机制
# 9 SPA首屏加载速度慢怎么解决
什么是首屏加载时间?
指的是浏览器从响应式用户输入网址地址,到首屏内容渲染完成的时间(虽然说不一定要全部渲染完成但是要展示当前视窗需要的内容)用户体验嘛
方法:分资源加载优化,渲染优化
- 文件体积
- 静态资源本地缓存
- UI按需加载
- 资源压缩
- 打包
- 优化代码
# 10 说说vue页面的渲染的流程
vue其实也就是一个应用实例数据驱动视图
初始化:创建应用实例,new Vue / createApp;初始化响应式系统,将data、props等用Proxy包装;生命周期 beforeCreate / created
模板编译:模板会先编译成 渲染函数,template → AST(抽象语法树)
AST → render 函数,可以返回虚拟 DOM(VNode)的函数
生成虚拟DOM:执行渲染函数,生成对应的虚拟 DOM 树
虚拟DOM -> 真实DOM:Vue 内部的 renderer 会把虚拟 DOM 转换成真实的 DOM 节点,挂载到页面上,渲染完成后触发生命周期
mounted。响应式更新:当数据变化时,Vue 触发更新流程
- 数据变更:Vue2:
setter触发依赖通知。 Vue3:Proxy set拦截触发依赖通知。 - Watcher / Effect 收集依赖:找到哪些组件(render 函数)依赖了这个数据,通知这些组件重新渲染
- 异步更新队列:
- Vue 并不会立即更新 DOM,而是把更新推入队列,利用 事件循环(microtask) 做批量更新。多次修改同一数据 → 只会更新一次。
- 重新执行 render 函数,生成新的虚拟 DOM 树(VNode),通过diff对比新旧虚拟 DOM,找出差异,只更新变化的部分 DOM 节点。
- 数据变更:Vue2:
卸载:移除组件、解绑事件监听、清除副作用(watcher/effect)
# 说说vite的原理
开发阶段:利用浏览器原生 ESM 做“按需编译”,开发快;
生产阶段:还是要打包,但用的是 Rollup、Rollup 做 Tree-shaking、代码分割,生成优化过的 bundle,打包优化,运行稳
# 11 vue中computed和watch的区别
computed 更像是声明式的“值”,适合数据衍生和缓存。
watch 更像是命令式的“动作”,适合处理异步任务和副作用。
# 12 computed怎么实现的缓存?
为什么 computed 需要缓存?
computed 的本质就是一个 基于依赖的“派生值”。如果不缓存,每次取值时都会重新执行 getter,性能很差,所以 Vue 的 computed 会在依赖(state、props 等)不变时,直接返回上次计算结果。
Vue2 用的是 Object.defineProperty + Watcher。
实现步骤:
- 定义
computed时,Vue 会为每个 computed 创建一个 lazy watcher。lazy = true,意味着这个 watcher 不会立即求值。
- 第一次访问 computed 属性时:
- 触发 getter → watcher.evaluate() → 计算值并缓存到
watcher.value。 - 并且收集依赖(data 中哪些属性被用到)。
- 触发 getter → watcher.evaluate() → 计算值并缓存到
- 以后再次访问 computed:
- 如果依赖没有变化,直接返回
watcher.value(缓存值)。 - 如果依赖变化,依赖的 setter 会触发 watcher.dirty = true,下次再访问时才重新计算。
- 如果依赖没有变化,直接返回
Vue3 用的是 effect + scheduler,实现更优雅。
实现原理:
computed(fn)内部会创建一个 reactive effect。- effect 默认不会立即执行,而是懒执行。
- 当依赖变化时,scheduler 会标记
dirty = true。 - 当再次访问 computed.value:
- 如果 dirty → 重新执行 getter,缓存结果。
- 如果没变 → 直接返回缓存。
# 13 computed可以依赖其他的computed吗
computed 也是一个响应式 ref(内部是 effect)。
一个 computed 依赖另一个 computed 时,本质上依然会收集依赖。
当底层数据变化,最内层 computed 先计算,外层 computed 的依赖也会被触发,从而更新。
# 14 VNode有哪些属性?
Vnode(虚拟 DOM 节点)是vue内部定义的对象,包含以下属性:
常见属性有:(vue3的)
- type:节点类型,可以是字符串(
div)、对象(组件)、Text、Comment、Fragment。 - props:VNode 的属性,包括
class、style、事件监听器(onClick等)。 - key:唯一标识,用于 Diff。
- ref:对应
ref特性。 - children:子 VNode 或字符串。
- el:对应的真实 DOM 节点。
- component:如果是组件 VNode,这里是组件实例。
- shapeFlag:位运算标志位,用来快速判断节点类型(如是元素、文本、组件、函数式组件等)。
- patchFlag:优化标志,标记节点哪些部分需要更新(静态提升、props 更新、class/style 变化等)。
- dynamicChildren:存放动态子节点,用于优化更新时跳过静态子树。
- appContext:应用上下文,包含全局配置、全局组件等。
# 15 Vuex相关的
# 说说vuex的原理?
Vuex 的原理是:利用 Vue 的响应式系统,让 store.state 成为全局响应式对象;通过 commit mutation 修改 state,再通过 dispatch action 执行异步逻辑;组件通过 getters/state 订阅数据变化,自动更新。
# 刷新浏览器后,vuex的数据是否存在?如何解决?
不会存在
原因是:
- Vuex 的数据(state)存放在内存里。
- 浏览器刷新 = 页面重新加载 → JS 重新执行 → 内存被清空 → Vuex 重新初始化。
- 所以刷新之后,Vuex 的状态会丢失,回到初始值。
解决方案:
使用 localStorage 或 sessionStorage 持久化存储。
使用 vuex-persistedstate 这样的插件自动化处理
# vuex有几种属性,他们存在的意义分别是什么?
1 state
- 定义:全局唯一的状态树,存放应用的数据源。
- 特点:响应式(基于 Vue 的响应式系统),组件里访问时
this.$store.state.xxx。 - 意义:集中式管理共享数据,避免跨组件通信混乱。
例子:
const store = new Vuex.Store({
state: {
user: { name: 'mmx' },
token: ''
}
})
2 getters
- 定义:类似组件的
computed,用来对 state 进行派生计算。 - 特点:有缓存,只有依赖的 state 变化时才重新计算。
- 意义:避免在多个组件里写重复的计算逻辑。
getters: {
isLogin: state => !!state.token,
userName: state => state.user.name.toUpperCase()
}
组件中访问:this.$store.getters.isLogin。
3 mutations
- 定义:修改 state 的唯一方式,必须是同步函数。
- 特点:接收 state 作为第一个参数,第二个参数是 payload。
- 意义:保证 state 的修改可追踪、可调试(devtools 可以记录 mutation 日志)。
例子:
mutations: {
setUser(state, user) {
state.user = user
}
}
调用:this.$store.commit('setUser', { name: 'mmx' })。
4 actions
- 定义:和 mutation 类似,但可以包含异步逻辑。
- 特点:通过
dispatch调用,内部最终还是commitmutation 来修改 state。 - 意义:把异步逻辑(如请求接口)从组件中抽离,保持数据流清晰。
例子:
actions: {
async fetchUser({ commit }) {
const user = await fetch('/api/user').then(r => r.json())
commit('setUser', user)
}
}
调用:this.$store.dispatch('fetchUser')。
5 modules
- 定义:将 store 拆分为模块,每个模块有独立的 state/getters/mutations/actions。
- 特点:支持嵌套模块,模块内部用
namespaced: true避免命名冲突。 - 意义:让大型应用的 store 更易维护。
例子:
const store = new Vuex.Store({
modules: {
user: {
namespaced: true,
state: () => ({ name: 'mmx' }),
mutations: { setName(state, name) { state.name = name } }
}
}
})
调用:this.$store.commit('user/setName', 'newName')。
# 16 vue-loader做了哪些事情
把 .vue 文件转换成浏览器可执行的 JS 模块。
一个 .vue 文件通常包含三部分:
<template>
<div>{{ msg }}</div>
</template>
<script>
export default { data: () => ({ msg: 'hello' }) }
</script>
<style scoped>
div { color: red; }
</style>
vue-loader 会把它拆开、编译、整合,最终变成 render 函数 + 脚本逻辑 + 样式处理 的 JS 模块。
工作流程:
- 解析 SFC:调用
@vue/compiler-sfc解析.vue文件。拆分模块 - 编译template:把模板编译成 render 函数(JS 函数,返回 VNode)。
- 处理script:直接提取
<script>部分的内容,看看有没有setup - 处理style:每个
<style>块交给其他 loader(如css-loader,postcss-loader,sass-loader)。,看看有没有scoped或者module - 拼装最终的模块:把
render函数、script导出对象、样式注入逻辑组合在一起。形成一个标准的 Vue 组件对象,供运行时挂载。
上面的示例会变成:
// script 部分
const script = { data: () => ({ msg: 'hello' }) }
// template 部分 -> render 函数
function render(_ctx, _cache) {
return h("div", null, _ctx.msg)
}
// style 部分(scoped 会自动加上 data-v-xxxx)
import "./App.vue?vue&type=style&index=0&id=xxxx&scoped=true"
// 最终导出组件
script.render = render
export default script
# 17 vue中给对象添加新属性时候,界面不刷新怎么办?
这个常见于vue2中
在 Vue 2 中,响应式系统是基于 Object.defineProperty 劫持对象的 已有属性 来实现的:
- 初始化时,Vue 会遍历 data 里的属性,把它们变成 getter/setter。
- 但是如果在运行时给对象 新增属性,Vue 没有机会劫持到这个属性的 getter/setter,所以它不会成为响应式。
- 结果就是界面不会更新。
如何处理:
方法 1:使用 Vue.set 或 this.$set
原理:Vue.set 会调用 defineReactive 给新属性设置 getter/setter,并触发依赖更新。
方法2:创建一个新对象替换
利用 对象解构 + 替换引用:
this.obj = { ...this.obj, newKey: 'value' }
- 原理:直接替换了整个对象引用,触发响应式更新。
方法 3:在初始化时定义好属性
即便是 null 或 undefined 占位,也要提前写好:
Vue3 中情况不同
- Vue3 使用 Proxy 实现响应式,可以劫持整个对象,不再局限于初始化时的属性。
- 所以在 Vue3 里
this.obj.newKey = 'value'就能正常触发更新了。
如果你在 Vue2 中频繁需要给对象动态加属性,你会怎么设计? 答:
- 尽量在
data初始化时定义所有可能的属性。 - 如果属性动态且不可预期,可以使用
Vue.set,或者用数组(Vue 对数组方法做了响应式处理)。
# 18 手写vue事件
vue3举例吧
然后例举事件:
$on、$emit、$off、$once
Vue3 版的事件系统(Event Bus)
// eventBus.js
class EventBus {
constructor() {
this.events = new Map(); // 用 Map 存储事件,key 是事件名,value 是回调数组
}
// 监听事件
on(event, callback) {
if (!this.events.has(event)) {
this.events.set(event, []);
}
this.events.get(event).push(callback);
}
// 触发事件
emit(event, ...args) {
if (this.events.has(event)) {
this.events.get(event).forEach(cb => cb(...args));
}
}
// 移除事件监听
off(event, callback) {
if (!this.events.has(event)) return;
if (!callback) {
// 如果没有传 callback,移除整个事件
this.events.delete(event);
} else {
// 移除某个指定的回调
const newCallbacks = this.events.get(event).filter(cb => cb !== callback);
this.events.set(event, newCallbacks);
}
}
// 只触发一次
once(event, callback) {
const wrapper = (...args) => {
callback(...args);
this.off(event, wrapper); // 执行完就移除
};
this.on(event, wrapper);
}
}
// 导出单例
const bus = new EventBus();
export default bus;
假设我们有两个组件:A.vue 和 B.vue,通过事件总线通信。
A.vue(触发事件)
<script setup>
import bus from './eventBus.js'
function sendMsg() {
bus.emit('hello', '来自 A 组件的数据');
}
</script>
<template>
<button @click="sendMsg">发送事件</button>
</template>
B.vue(监听事件)
<script setup>
import { onMounted, onUnmounted } from 'vue'
import bus from './eventBus.js'
function handler(msg) {
console.log("B 组件收到消息:", msg);
}
onMounted(() => {
bus.on('hello', handler)
})
onUnmounted(() => {
bus.off('hello', handler) // 组件卸载时清理监听
})
</script>
<template>
<p>B 组件</p>
</template>
推荐 defineEmits + emits 机制
# 19 vue中,created和mounted两个钩子之间调用时间的差值
- 模板编译时间
- 虚拟DOM渲染时间
- 异步操作
- 浏览器性能
- 其他的构造的时间:beforeCreate、created这些
# 20 vue3的性能提升在哪些方面?设计目标是什么?
性能提升点
响应式系统优化:Proxy 代替 Object.defineProperty:Vue3 使用
Proxy,可以直接代理整个对象,天然支持:数组、Map、Set、深层嵌套对象,性能更优,代码量更小。编译优化:静态提升 & Patch Flag
更小的打包体积:Vue3 用 Tree-Shaking 友好设计(ESM 模块化),只打包用到的 API。Vue2 是整体打包,Tree-Shaking 效果不佳。
Fragment 支持(多根节点)
Vue2:一个组件必须有一个根节点,多余节点需要
<div>包裹,导致无意义的 DOM 增加。Vue3:支持 Fragment,可以有多个根节点,减少无效 DOM。
更高效的 Diff 算法
服务端渲染(SSR)优化:Vue3 SSR 速度比 Vue2 快 2~3 倍。
更好的 TypeScript 支持:Vue3 内核用 TS 重写,天然类型安全,开发体验更好。
# 21 Vue模板是如何编译的?
编译分三步(类比翻译过程)
解析 (Parse) → 把模板拆开,看懂结构
- 类似把一句话拆成“主语、谓语、宾语”。
- 结果是一个 AST 树,比如:
- div 标签
- p 标签
- p 里面有个 动态内容
优化 (Optimize) → 标记哪些是静态的
比如模板里有:
<h1>固定标题</h1> <p>{{ message }}</p><h1>永远不会变,就打个“静态”的标记,更新时就可以跳过它。
生成 (Codegen) → 翻译成 render 函数
最后变成 JS 代码:
function render() { return createElement("div", { id: "app" }, [ createElement("p", [toString(message)]) ]) }
运行时
- Vue 执行
render函数,得到 虚拟 DOM(VNode 树)。 - 然后把虚拟 DOM “挂”到真实的 DOM 上。
# 22 说说vue中的CSS scoped的原理
你在 <style scoped> 里写样式时,Vue 会做一层编译时的处理,本质是 给当前组件 DOM 元素动态加一个“独特的属性标识”,并且修改 CSS 选择器,使它们只作用于当前组件的 DOM。
假设你写了这样一个组件:
<template>
<div class="box">Hello</div>
</template>
<style scoped>
.box {
color: red;
}
</style>
编译后(简化后)会变成:
<!-- 渲染出来的 DOM -->
<div class="box" data-v-123abc>Hello</div>
/* 编译后的 CSS */
.box[data-v-123abc] {
color: red;
}
- Vue 会给当前组件的根节点及其子节点加上一个 哈希属性(如
data-v-123abc)。 - 同时把你写的选择器都变成 带这个属性选择器的形式。
- 这样,样式就只会作用于当前组件的元素,避免全局污染。
那如果我想在父组件里覆盖子组件的样式怎么办?
答:
scoped默认不会穿透到子组件,你需要用::v-deep:::v-deep(.child-class) { color: blue; }
那 scoped 和 CSS Modules 有什么区别?
- scoped:通过给 DOM 节点加属性标记 + 选择器重写,隔离样式。
- CSS Modules:通过类名哈希化(
box → box__hash123),达到样式隔离。 - Vue3 支持两者,可以同时使用。
在 Vue3 中还有什么替代 scoped 的方法?
- Vue3 还支持:
- CSS Modules
- CSS-in-JS(如 styled-components、UnoCSS)
- Scoped + :global / :deep / :slotted
scoped 会不会带来性能问题?
- 一般影响不大,因为只是多了一个属性选择器(
[data-v-xxx]),浏览器解析效率足够快。 - 但是在 大量节点、深度嵌套选择器 时,可能有微小性能损耗。
# 23 如何打破scope对样式隔离的限制?
使用深度选择器
::v-deep告诉编译器,这部分选择器不要加 data-v-xxx 的限制使用
:global告诉 Vue:这个样式是全局的,不要加 data-v-xxx。可以影响子组件。使用
:slotted(Vue3 专用)专门针对 插槽内容 的样式控制。
全局样式
为什么要有 scoped,还要想办法打破它?
- 因为大多数时候我们要保证组件样式隔离,避免全局污染。
- 但在业务场景下,我们经常需要覆盖第三方组件库的样式(如 Element Plus、Ant Design Vue),这时候就需要打破隔离。
# 24 vue中$route和$router有什么区别?
$route
- 表示 当前激活的路由信息对象(location 对象)。
- 包含了当前路由的 路径、参数、查询、哈希等信息。
- 是 响应式对象,当路由发生变化时,依赖它的组件会重新渲染。
常见属性:
$route.path:当前路径(/home/list)。$route.params:路由参数(动态路由:id的值)。$route.query:URL 查询参数(?a=1&b=2)。$route.hash:哈希值(#section1)。$route.fullPath:完整路径(包含 query + hash)。$route.matched:匹配到的路由记录数组。
$router
- 表示 路由实例(VueRouter 对象)。
- 提供了导航方法,用来进行 路由跳转和控制。
常见方法:
$router.push(location):跳转到一个新路由,会在 history 栈里新增一条记录。$router.replace(location):跳转但不会新增 history 记录,而是替换当前。$router.go(n):在 history 记录中前进或后退 n 步。$router.back():相当于go(-1)。$router.forward():相当于go(1)。$router.beforeEach:全局前置守卫。
# v-model的原理
# vue3的setup原理
你做过什么针对vue的相关优化