简历写精通 Vue 却频频挂面试?这篇真题救你 offer(一)

四季读书网 2 0
简历写精通 Vue 却频频挂面试?这篇真题救你 offer(一)
简历写精通 Vue 却频频挂面试?这篇真题救你 offer(一)-第1张图片-四季读书网

📋 目录

  • Q1. Vue 的核心是什么?对比 Vue 2 和 Vue 3 的响应式原理有什么区别

  • Q2. 什么是 MVVM 模式?Vue 是如何体现 MVVM 的?

  • Q3. Vue 实例的选项有哪些?(例如 data、computed、methods、watch、生命周期等)

  • Q4. v-if 和 v-show 有什么区别?分别适用于什么场景?

  • Q5. v-for 和 v-if 为什么不推荐一起使用?有什么替代方案?

  • Q6. Vue 中如何实现父子组件通信?(props + $emit / v-model)

  • Q7. 什么是插槽(Slot)?slot 和 slot-scope 在 Vue 2 和 Vue 3 中有什么区别?

  • Q8. computed 和 watch 有什么区别?各自的使用场景是什么?

  • Q9. 什么是 nextTick?它的实现原理是什么?

  • Q10. Vue 3 的 Composition API 和 Options API 有什么区别?


Q1. Vue 的核心是什么?对比 Vue 2 和 Vue 3 的响应式原理有什么区别

考点

Vue 渐进式框架理念、响应式数据绑定的核心机制、Vue 2 与 Vue 3 响应式实现原理的底层差异。

答案

Vue 的核心响应式数据绑定组件化系统简历写精通 Vue 却频频挂面试?这篇真题救你 offer(一)-第2张图片-四季读书网

Vue 是一个渐进式的 JavaScript 框架,所谓"渐进式"是指它可以按需使用:可以用 CDN 引入做局部页面增强,也可以构建完整的单页应用(结合 Vue Router、Vuex/Pinia、Vite 等)。其核心思想有三点:

  • 响应式数据绑定:数据(Model)和视图(View)自动同步,当数据变化时视图自动更新,开发者无需手动操作 DOM。

  • 组件化系统:将 UI 拆分为独立、可复用的组件,每个组件包含自己的模板、逻辑、样式。

  • 声明式渲染:用模板语法描述"想要什么样子",由 Vue 负责"怎么做到"。

Vue 2 的响应式原理简历写精通 Vue 却频频挂面试?这篇真题救你 offer(一)-第3张图片-四季读书网简历写精通 Vue 却频频挂面试?这篇真题救你 offer(一)-第4张图片-四季读书网

Vue 2 通过Object.defineProperty拦截对象属性的读取和设置,在读取时收集依赖(哪些地方用到了这个数据),在设置时通知依赖更新。它的核心思路是:把 data 中定义的每个属性都转换成带有 getter/setter 的"响应式属性",当属性被访问时把当前的 Watcher 收集到依赖列表中,属性被修改时再依次通知这些 Watcher 重新执行。

但 Vue 2 这种实现方式存在三个明显缺陷:

  1. 无法监听对象新增/删除属性:因为Object.defineProperty只能拦截已存在的属性,属性在初始化时就被处理完毕。后续给对象添加新字段,Vue 2 不会感知,必须通过Vue.set/this.$set手动注入响应式。

  2. 无法监听通过数组下标直接赋值和修改数组长度:Vue 2 出于性能考虑对数组做了特殊处理,没有为数组的每个下标建立响应式。要触发更新必须使用数组的"变异方法"(push、pop、shift、unshift、splice、sort、reverse),或者通过this.$set/this.$delete显式处理。

  3. 必须递归遍历嵌套对象:初始化时就要深度遍历整个 data,对所有嵌套属性调用Object.defineProperty,首次渲染开销大。

Vue 3 的响应式原理

Vue 3 使用 ES6 的Proxy重写了响应式系统。Proxy可以理解成"对象的代理器",可以在代理对象的读取、设置、新增、删除、in 操作等 13 种行为上做拦截,从根本上解决了Object.defineProperty的限制。

Vue 3 的响应式建立在三个核心函数之上:

  • reactive:把一个普通对象包装成 Proxy 代理对象,访问属性时自动追踪依赖,修改属性时自动触发更新。同时采用"懒递归"策略——只有真正访问到嵌套对象时才会把它也包装成 reactive,避免初始化时的全量递归。

  • effect / watch:用来声明"副作用",比如更新 DOM、发起网络请求等。它会立即执行一次传入的函数,并在函数内部访问到的所有响应式属性上注册自己,当这些属性变化时 effect 会自动重新执行。

  • track / trigger:依赖追踪与触发更新的底层机制。track 在 getter 中收集"哪个 effect 依赖了哪个属性",trigger 在 setter 中根据属性找到所有依赖的 effect 并依次执行。

Vue 2 vs Vue 3 响应式对比

  • 新增属性响应式:Vue 2 需要$set,Vue 3 原生支持。

  • 删除属性响应式:Vue 2 需要$delete,Vue 3 原生支持。

  • 数组索引赋值:Vue 2 需要$set,Vue 3 原生支持。

  • 嵌套对象递归方式:Vue 2 初始化时全量递归,Vue 3 是按需懒递归。

  • 性能:Vue 2 首次渲染开销大,Vue 3 性能更优。

  • 支持的数据类型:Vue 2 只支持对象/数组,Vue 3 的 Proxy 还支持 Map、Set、WeakMap、WeakSet 等。

实际项目中的区别

在 Vue 2 中如果要给表单动态加字段,必须调用this.$set(this.form, 'newField', value),否则输入框不会响应。在 Vue 3 中直接form.newField = value即可,Proxy 会自动追踪这个新属性。这也是为什么 Vue 3 在处理动态表单、动态列配置等场景时比 Vue 2 优雅得多。

知识点详解

渐进式框架

"渐进式"的核心含义是不强制全栈。开发者可以从最简单的场景开始使用 Vue:

// 第一步:只做模板字符串替换new Vue({  el'#app',  data: { message'Hello' }});

随着项目复杂度提升,可以逐步引入组件系统、Vue Router、Vuex/Pinia、构建工具等。这种设计降低了学习曲线,也避免了"为了用框架而重构项目"的问题。

Object.defineProperty 原理

Object.defineProperty是 ES5 的 API,可以拦截对象属性的读取和赋值:

// 简化版 Vue 2 响应式实现function defineReactive(obj, key, val) {  const dep = new Dep(); // 依赖收集器  Object.defineProperty(obj, key, {    get() {      // 读取时收集依赖:把当前 Watcher 加入 dep      dep.depend();      return val;    },    set(newVal) {      if (newVal === val) return;      val = newVal;      // 设置时通知更新:dep 中所有 Watcher 重新执行      dep.notify();    }  });}function observe(obj) {  // 遍历所有属性,逐个 defineReactive  Object.keys(obj).forEach(key => defineReactive(obj, key, obj[key]));}const data = { count0 };observe(data);data.count;     // 触发 get,收集依赖data.count = 1// 触发 set,通知更新

关键限制:必须在observe阶段已知属性才能拦截。所以 Vue 2 在初始化时就要遍历整个 data 对象,对每个属性调用Object.defineProperty。后续动态添加的属性不会被拦截——这就是Vue.set存在的意义。

const vm = new Vue({ data: { user: { name'张三' } } });// ❌ 不会触发更新:age 不在初始化时定义的属性中vm.user.age = 18;// ✅ 手动添加响应式Vue.set(vm.user, 'age'18);// 或 this.$set(vm.user, 'age', 18)

Proxy 原理

ES6 的Proxy可以"代理"整个对象,拦截更多操作:

// 简化版 Vue 3 reactive 实现function reactive(target) {  return new Proxy(target, {    // 拦截属性读取    get(target, key, receiver) {      const result = Reflect.get(target, key, receiver);      // 追踪依赖      track(target, key);      // 懒递归:访问到嵌套对象时才包装      if (isObject(result)) return reactive(result);      return result;    },    // 拦截属性赋值    set(target, key, value, receiver) {      const result = Reflect.set(target, key, value, receiver);      // 触发更新      trigger(target, key);      return result;    },    // 拦截属性删除    deleteProperty(target, key) {      const result = Reflect.deleteProperty(target, key);      trigger(target, key);      return result;    },    // 拦截 in 操作    has(target, key) {      const result = Reflect.has(target, key);      track(target, key);      return result;    }  });}

相比Object.defineProperty,Proxy 有三大优势:

  • 支持新增/删除属性:不需要$set/$delete,直接obj.newKey = value即可。

  • 支持数组索引赋值:直接arr[0] = newValue会触发更新。

  • 懒递归:只有真正访问嵌套对象时才包装为 reactive,初始化开销小。

依赖收集与派发更新

响应式系统的核心是三个角色:数据源、副作用、调度器

// 简化的依赖收集与触发const targetMap = new WeakMap(); // target -> key -> Set<effect>function track(target, key) {  let depsMap = targetMap.get(target);  if (!depsMap) targetMap.set(target, (depsMap = new Map()));  let dep = depsMap.get(key);  if (!dep) depsMap.set(key, (dep = new Set()));  dep.add(activeEffect); // 把当前活跃的 effect 加入依赖}function trigger(target, key) {  const depsMap = targetMap.get(target);  if (!depsMap) return;  const dep = depsMap.get(key);  if (dep) dep.forEach(effect => effect.run()); // 依次执行 effect}let activeEffect = null;function effect(fn) {  activeEffect = { run: fn };  fn(); // 立即执行,期间访问到的响应式数据会自动建立依赖  activeEffect = null;}

关键点

  • track在 getter 中调用,收集"哪个 effect 依赖了哪个 key"。

  • trigger在 setter 中调用,触发"所有依赖这个 key 的 effect"重新执行。

  • effect 嵌套时用栈管理,清理时恢复到外层 effect。

批量更新与异步调度

Vue 不会在数据变化时立即执行 effect,而是合并到下一个 tick 批量执行:

// 简化的调度器let queue = new Set();let isFlushing = false;function queueJob(job) {  queue.add(job);  if (!isFlushing) {    isFlushing = true;    // 用微任务在同步代码执行完后批量执行    Promise.resolve().then(flushJobs);  }}function flushJobs() {  queue.forEach(job => job());  queue.clear();  isFlushing = false;}

为什么用微任务?微任务在同步代码结束后立即执行(在渲染前),能保证 nextTick 回调在 DOM 更新后被调用。

ref 与 reactive 的区别

import { ref, reactive } from 'vue';// ref 包装基本类型const count = ref(0);console.log(count.value); // 0count.value++;            // 1// reactive 包装对象const state = reactive({ name'张三'age25 });console.log(state.name);  // '张三',不需要 .valuestate.age = 30;// 模板中 ref 会被自动解包// {{ count }} 等价于 {{ count.value }}

选择建议

  • 基本类型 →ref

  • 对象/数组 →reactive(属性访问更简洁)。

  • 需要整体替换的对象 →ref(reactive 整体替换会丢失引用)。

  • 大型项目统一用ref,避免混用带来的认知负担。


Q2. 什么是 MVVM 模式?Vue 是如何体现 MVVM 的?

考点

MVVM 架构模式的核心概念、Vue 与 MVVM 的对应关系、MVVM 相比 MVC/MVP 的优势。

答案

MVVM(Model-View-ViewModel)是一种软件架构模式,核心思想是让 View 和 Model 通过 ViewModel 实现自动双向同步,减少手动 DOM 操作。

MVVM 三层职责

  • View(视图层):用户能看到的界面,负责把数据渲染出来并接收用户输入。在 Vue 中对应模板(template)。

  • ViewModel(视图模型层):是 View 和 Model 之间的桥梁,一方面把 Model 转换成 View 需要的展示数据(数据格式化、过滤、计算),另一方面处理 View 层的交互逻辑(事件处理)。在 Vue 中对应 Vue 实例本身(data + methods + computed + watch 共同构成 ViewModel)。

  • Model(数据层):业务数据和业务逻辑,通常来自后端接口、JSON 数据或本地状态。在 Vue 中对应 data 中存储的原始数据(最终可能来自后端 API)。

Vue 中 MVVM 的体现

Vue 是 MVVM 模式的典型实现。开发者只需要关注"数据是什么样子"和"视图怎么展示数据"两部分,View 和 Model 之间的同步工作完全由 Vue 框架自动完成

  • 当 Model(data 中的数据)发生变化时,View(模板)会自动重新渲染。

  • 当用户在 View 上输入(如 v-model 绑定输入框)时,Model 也会自动更新。

  • 整个过程中开发者不需要写document.getElementByIdinnerHTML =这种命令式代码。

MVVM 与 MVC/MVP 的区别

  • MVC(Model-View-Controller):Controller 负责处理所有业务逻辑和流程控制,但随着业务膨胀,Controller 容易变得臃肿,且 View 通常直接和 Controller 通信。

  • MVP(Model-View-Presenter):Presenter 持有 View 的引用,代替 View 处理业务逻辑,View 和 Presenter 是耦合的(Presenter 需要知道 View 的存在)。

  • MVVM:ViewModel 不持有 View 的引用,通过数据绑定实现自动同步,View 和 ViewModel 是解耦的,开发体验最好。

实际项目中的 MVVM 应用

以电商订单列表页为例:

  • Model:从后端/api/orders接口返回的订单数组,每条订单有订单号、状态、金额等字段。

  • View:订单搜索框、状态筛选下拉框、订单卡片列表(用 v-for 渲染)、空状态提示。

  • ViewModel

    • data 中保存原始订单列表、loading 状态、搜索关键词、筛选条件。

    • computed 中根据搜索关键词和筛选条件过滤订单,得到"视图需要的格式"。

    • watch 中监听订单变化,触发统计更新等副作用。

    • methods 中处理"重置筛选""跳转到详情页"等交互。

整个过程中,View 完全不知道 Model 怎么来的,Model 也不知道 View 长什么样,ViewModel 是它们的协调者。

知识点详解

双向绑定的本质

MVVM 强调的"双向"实际上是由两个单向构成的:Model → View(数据驱动视图)和 View → Model(视图驱动数据)。Vue 中的v-model把这两个单向绑定封装成了语法糖:

<!-- v-model 的本质:value 绑定 + input 事件监听 --><input:value="message" @input="message = $event.target.value" /><!-- 完全等价于 --><inputv-model="message" />

React 中没有 v-model 语法糖,必须手动实现:

// React 中的双向绑定(手动实现)function App() {  const [message, setMessage] = useState('');  return (    <input      value={message}      onChange={e => setMessage(e.target.value)}    />  );}

ViewModel 为什么不持有 View 引用

这是 MVVM 与 MVP 最大的区别。

// MVP 风格:Presenter 直接调用 View 的方法(强耦合)classPresenter {constructor(view) {this.view = view; // 持有 View 引用  }  loadData() {    fetch('/api/data').then(data => {this.view.showLoading();    // 调用 Viewthis.view.renderList(data); // 调用 View    });  }}// MVVM 风格:ViewModel 只维护数据,View 通过数据驱动渲染new Vue({data: { loading: false, list: [] },  created() {    fetch('/api/data').then(data => {this.loading = false// 改数据,View 自动更新this.list = data;     // 改数据,View 自动更新    });  }});

优势:ViewModel 不需要知道 View 的存在,单元测试更容易(直接测试数据逻辑),跨端复用更容易(同一份 ViewModel 可对接 Web、小程序、App 的不同 View)。

数据驱动视图的演进

前端开发的范式经历了三个阶段:

阶段代表思维模型典型 API
命令式 DOM 操作jQuery"我要怎么改 DOM"$el.html('...')
模板引擎Mustache/Handlebars"模板 + 数据 = HTML"Mustache.render(tpl, data)
数据驱动视图Vue/React"我维护好数据,框架帮我更新视图"this.list = newList

MVVM 是数据驱动视图的典型实现,开发者从"如何操作 DOM"转变为"如何维护数据状态",思维模型发生了根本变化。

MVVM 与响应式的关系

// 同样的需求在两种模式下的实现// 1. 命令式:手动监听变化 + 手动更新 DOMconst data = { count0 };const el = document.getElementById('count');Object.defineProperty(data, 'count', {  set(newVal) {    el.textContent = newVal; // 手动更新 DOM  }});// 2. MVVM:声明式,框架自动同步new Vue({  data: { count0 },  template'<div>{{ count }}</div>' // 数据变化自动更新});

MVVM 是一种架构思想,响应式是实现手段。Vue 通过响应式系统(Proxy/Object.defineProperty)实现了 MVVM 中的"自动同步"。但 MVVM 并不限于 Vue——Knockout.js、AngularJS、San.js 等都遵循 MVVM 思想,只是实现方式不同。

编编留言:

内容篇幅有些长,将题目做拆解,下一篇再继续学习,加油!各位童鞋简历写精通 Vue 却频频挂面试?这篇真题救你 offer(一)-第5张图片-四季读书网简历写精通 Vue 却频频挂面试?这篇真题救你 offer(一)-第6张图片-四季读书网简历写精通 Vue 却频频挂面试?这篇真题救你 offer(一)-第7张图片-四季读书网

抱歉,评论功能暂时关闭!