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中父组件怎么监听到子组件的生命周期

  1. 子组件事件通信emit,适合单向数据流
  2. 使用ref拿到子组件实例,等子组件挂载好主动调用方法

# 8 为什么react需要fiber架构,而vue却不需要

fiber架构使得react实现异步渲染和任务优先级变得更加容易

然后vue的话也不是不需要是采取另外一种响应式系统和渲染机制

# 9 SPA首屏加载速度慢怎么解决

什么是首屏加载时间?

指的是浏览器从响应式用户输入网址地址,到首屏内容渲染完成的时间(虽然说不一定要全部渲染完成但是要展示当前视窗需要的内容)用户体验嘛

方法:分资源加载优化,渲染优化

  • 文件体积
  • 静态资源本地缓存
  • UI按需加载
  • 资源压缩
  • 打包
  • 优化代码

# 10 说说vue页面的渲染的流程

vue其实也就是一个应用实例数据驱动视图

  1. 初始化:创建应用实例,new Vue / createApp;初始化响应式系统,将data、props等用Proxy包装;生命周期 beforeCreate / created

  2. 模板编译:模板会先编译成 渲染函数,template → AST(抽象语法树)

    AST → render 函数,可以返回虚拟 DOM(VNode)的函数

  3. 生成虚拟DOM:执行渲染函数,生成对应的虚拟 DOM 树

  4. 虚拟DOM -> 真实DOM:Vue 内部的 renderer 会把虚拟 DOM 转换成真实的 DOM 节点,挂载到页面上,渲染完成后触发生命周期 mounted

  5. 响应式更新:当数据变化时,Vue 触发更新流程

    • 数据变更:Vue2:setter 触发依赖通知。 Vue3:Proxy set 拦截触发依赖通知。
    • Watcher / Effect 收集依赖:找到哪些组件(render 函数)依赖了这个数据,通知这些组件重新渲染
    • 异步更新队列:
    • Vue 并不会立即更新 DOM,而是把更新推入队列,利用 事件循环(microtask) 做批量更新。多次修改同一数据 → 只会更新一次。
    • 重新执行 render 函数,生成新的虚拟 DOM 树(VNode),通过diff对比新旧虚拟 DOM,找出差异,只更新变化的部分 DOM 节点。
  6. 卸载:移除组件、解绑事件监听、清除副作用(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

实现步骤:

  1. 定义 computed 时,Vue 会为每个 computed 创建一个 lazy watcher
    • lazy = true,意味着这个 watcher 不会立即求值。
  2. 第一次访问 computed 属性时:
    • 触发 getter → watcher.evaluate() → 计算值并缓存到 watcher.value
    • 并且收集依赖(data 中哪些属性被用到)。
  3. 以后再次访问 computed:
    • 如果依赖没有变化,直接返回 watcher.value(缓存值)。
    • 如果依赖变化,依赖的 setter 会触发 watcher.dirty = true,下次再访问时才重新计算。

Vue3 用的是 effect + scheduler,实现更优雅。

实现原理:

  1. computed(fn) 内部会创建一个 reactive effect
  2. effect 默认不会立即执行,而是懒执行。
  3. 当依赖变化时,scheduler 会标记 dirty = true
  4. 当再次访问 computed.value:
    • 如果 dirty → 重新执行 getter,缓存结果。
    • 如果没变 → 直接返回缓存。

# 13 computed可以依赖其他的computed吗

computed 也是一个响应式 ref(内部是 effect)。

一个 computed 依赖另一个 computed 时,本质上依然会收集依赖。

当底层数据变化,最内层 computed 先计算,外层 computed 的依赖也会被触发,从而更新。

# 14 VNode有哪些属性?

Vnode(虚拟 DOM 节点)是vue内部定义的对象,包含以下属性:

常见属性有:(vue3的)

  • type:节点类型,可以是字符串(div)、对象(组件)、TextCommentFragment
  • props:VNode 的属性,包括 classstyle、事件监听器(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 调用,内部最终还是 commit mutation 来修改 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 模块。

工作流程:

  1. 解析 SFC:调用 @vue/compiler-sfc 解析 .vue 文件。拆分模块
  2. 编译template:把模板编译成 render 函数(JS 函数,返回 VNode)。
  3. 处理script:直接提取 <script> 部分的内容,看看有没有setup
  4. 处理style:每个 <style> 块交给其他 loader(如 css-loader, postcss-loader, sass-loader)。,看看有没有scoped或者module
  5. 拼装最终的模块:把 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.setthis.$set

原理:Vue.set 会调用 defineReactive 给新属性设置 getter/setter,并触发依赖更新。

方法2:创建一个新对象替换

利用 对象解构 + 替换引用

this.obj = { ...this.obj, newKey: 'value' }
  • 原理:直接替换了整个对象引用,触发响应式更新。

方法 3:在初始化时定义好属性

即便是 nullundefined 占位,也要提前写好:

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.vueB.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两个钩子之间调用时间的差值

  1. 模板编译时间
  2. 虚拟DOM渲染时间
  3. 异步操作
  4. 浏览器性能
  5. 其他的构造的时间:beforeCreate、created这些

# 20 vue3的性能提升在哪些方面?设计目标是什么?

性能提升点

  1. 响应式系统优化:Proxy 代替 Object.defineProperty:Vue3 使用 Proxy,可以直接代理整个对象,天然支持:数组、Map、Set、深层嵌套对象,性能更优,代码量更小。

  2. 编译优化:静态提升 & Patch Flag

  3. 更小的打包体积:Vue3 用 Tree-Shaking 友好设计(ESM 模块化),只打包用到的 API。Vue2 是整体打包,Tree-Shaking 效果不佳。

  4. Fragment 支持(多根节点)

    Vue2:一个组件必须有一个根节点,多余节点需要 <div> 包裹,导致无意义的 DOM 增加

    Vue3:支持 Fragment,可以有多个根节点,减少无效 DOM。

  5. 更高效的 Diff 算法

  6. 服务端渲染(SSR)优化:Vue3 SSR 速度比 Vue2 快 2~3 倍。

  7. 更好的 TypeScript 支持:Vue3 内核用 TS 重写,天然类型安全,开发体验更好。

# 21 Vue模板是如何编译的?

编译分三步(类比翻译过程)

  1. 解析 (Parse) → 把模板拆开,看懂结构

    • 类似把一句话拆成“主语、谓语、宾语”。
    • 结果是一个 AST 树,比如:
      • div 标签
      • p 标签
      • p 里面有个 动态内容
  2. 优化 (Optimize) → 标记哪些是静态的

    • 比如模板里有:

      <h1>固定标题</h1>
      <p>{{ message }}</p>
      

      <h1> 永远不会变,就打个“静态”的标记,更新时就可以跳过它。

  3. 生成 (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对样式隔离的限制?

  1. 使用深度选择器 ::v-deep告诉编译器,这部分选择器不要加 data-v-xxx 的限制

  2. 使用 :global 告诉 Vue:这个样式是全局的,不要加 data-v-xxx。可以影响子组件。

  3. 使用 :slotted(Vue3 专用)

    专门针对 插槽内容 的样式控制。

  4. 全局样式

为什么要有 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的相关优化