1.data为什么是一个函数而不是对象
维度 | 对象形式 | 函数形式 |
---|---|---|
数据隔离性 | 所有实例共享同一对象,导致数据污染 | 每个实例拥有独立数据副本 |
复用安全性 | 不适用于可复用组件 | 支持组件安全复用 |
语言机制 | 引用传递引发副作用 | 函数返回值实现作用域隔离(闭包) |
框架约束 | 仅根实例允许使用 | 组件必须使用 |
data 函数在每次调用时形成闭包,返回的对象属于当前组件实例的作用域链,不会被其他实例访问或修改
2. Proxy 比 defineProperty到底好在哪
Proxy 的优势本质在于其设计理念的先进性:以对象为颗粒度的代码模式取代了 defineProperty 以属性为颗粒度的拦截模式,这使得 Proxy 在功能覆盖、性能优化和开发体验式均实现了显著提升。这也是以 Vue3 选择 Proxy 重构响应式系统的核心原因。
Proxy 是 ES6+,不支持 IE11 等旧浏览器,而 defineProperty(ES 5)兼容性更广。
3. $nextTick 的原理及作用?
$nextTick 等待下一次 DOM 更新刷新的工具方法。
Vue 的 nextTick 其本质是对 JavaScript 执行原理的 EventLoop 的应用。Vue 的 DOM 更新是异步的,当数据变化时,Vue 会将 DOM 更新的操作缓存在一个队列中,等待同一事件循环内的所有数据变化完成后,在同一进行试图更新。这种机制通过合并多次数据变更 减少不必要的 DOM 操作,优化性能。
源码实现机制:
- 回调队列(callbacks): $nextTick 将用户传入的回调函数推入队列,保证多个 $nextTick 调用合并为一个异步任务;
- 执行异步函数(timerFunc):根据浏览器支持的情况选择最优异步方案:
- 微任务优先:Promise>MutationObserver>setImmediate>setTimeout
- 兼容性处理:旧版浏览器降级为宏任务
- 防止重复执行(pending):通过标志位确保同一时间只执行一次队列刷新
作用:
- 获取更新后的 DOM(在特定的生命周期【created、mounted】中确保 DOM 就绪)
- 优化高频操作
4.对 keep-alive 的核心理解与实现原理分析?
keep-alive 是 vue 中的内置组件,能在组件切换过程中将状态仍保留在内存中,防止重复渲染 DOM
props 属性:
- include - 字符串或正则表达式,只有名称匹配的组件会被缓存
- exclude - 字符串或者正则表达式,任何名字匹配的组件都不会被缓存
- max - 数字,最多可以缓存多少组件实例
核心理解: keep-alive 是 Vue.js 的内置组件,其核心作用是 通过缓存不活跃的组件实例,避免重复销毁和重建,从而提高性能并保留组件状态(如表单输入、滚动位置等)。其本质是通过内存管理实现组件的 “失活” 与 “激活”。
实现原理:
- 存储结构:keep-alive 内部维护一个缓存对象(cache)和一个 缓存键数组(keys),用于存储组件实例的虚拟节点(vnode)及其唯一标识 key。
- 复用逻辑:当组件切换时,若匹配[ include, exclude,max]到缓存中的 key,则直接复用缓存的 vnode 及其管理实例(componentInstance)和 DOM 元素($el)
5.Vue 单页应用与多页应用的区别?
SPA 是一种 网络应用程序或者网站的模型
优点:
- 具有桌面应用的即时性、网站的可移植性和可访问性
- 用户体验好、快,内容的改变不需要更新加载整个页面
- 良好的前后端分离,分工更明确
缺点: - 不利于搜索引擎的抓取
- 首次渲染速度相对较慢
对比维度 | 单页应用(SPA) | 多页应用(MPA) |
---|---|---|
页面加载方式 | 首次加载全部资源,后续局部动态更新(无刷新) | 每次跳转加载完整新页面(整页刷新) |
用户体验 | 切换流畅,接近原生应用 | 切换有延迟,传统网页体验 |
开发复杂度 | 较高(需前端路由、状态管理、组件化) | 较低(传统多页面开发,逻辑分散) |
技术栈 | Vue/React + 前端路由(Vue Router) + 状态管理(Vuex/Pinia) | 多页面独立 HTML,可能搭配后端模板(如 JSP/PHP) |
资源复用 | 公共资源(JS/CSS)仅加载一次 | 公共资源可能重复加载 |
首屏性能 | 首屏较慢(需加载框架代码),需代码分割优化 | 首屏较快(仅加载当前页资源) |
路由机制 | 前端路由(Hash/History 模式) | 后端路由或传统 < a > 标签跳转 |
SEO 支持 | 需额外优化(如 SSR、预渲染) | 天然支持(静态内容易被抓取) |
适用场景 | 交互复杂的中后台系统、Web 应用(如在线工具、社交平台) | 内容为主的网站(如企业官网、博客) |
维护成本 | 组件化提升复用性,但大型项目状态管理复杂 | 重复代码多,多页面维护成本高 |
数据传递 | 全局状态管理(如 Vuex)或组件通信 | URL 参数、Cookie、LocalStorage |
安全性 | 前端逻辑暴露较多,需防范 XSS/CSRF | 后端处理核心逻辑,安全性较高 |
服务器压力 | 首屏后请求少(API 交互为主) | 每次跳转均需服务器返回完整页面 |
6. Vue 模板到 Render 函数的编译过程解析?
解析模板生成 AST => 静态节点优化 => 生成渲染函数
- 模板解析阶段: 生成抽象语法树(AST),将非结构化的模板字符串转换为可编程操作的树形结构数据,描述模板的语法结构。
- 词法分析: 使用正则表单时逐字符解析模板,识别 HTML 标签、属性、指令(v-if)、插值表达式({{}})等。
- 语法分析:构建树形结构 AST,每个节点表示一个元素或表达式
- 关键模板:Vue 的 parse 函数
- 优化阶段: 标记静态节点,通过跳过静态节点的比对减少虚拟 DOM 更新时的计算量,提升渲染效率
- 静态节点识别: 深度遍历 AST,标记不会变化的 节点(如纯文本、无动态绑定的元素)
- 静态提升(vue3):将静态节点提升到渲染函数的外部,避免重复创建
- 关键模块:optimizer 模板、
- 代码生成阶段:生成渲染函数,将优化后的 AST转化为可执行的 Javascript 代码(render 函数),生成轻量级的虚拟 DOM 描述,供运行时渲染器高效的更新视图。
- 递归遍历 AST:根据节点类型生成对应的 Javascript 代码片段。
- 拼接代码字符串:使用 _c(创建元素)、_v(创建文本节点)、_s(字符串化数据)等辅助函数构建虚拟 DOM
- 关键模块: generate 函数
7. 描述一下 Vue 自定义指令
Vue 自定义指令是一种针对 DOM 元素底层操作的复用逻辑机制,允许开发者封装(如焦点控制、样式操作、事件监听等行为)。与组件(复用 UI 逻辑)和 组合式函数(复用状态逻辑)不同,自定义指令专注于直接操作 DOM ,适合解决跨组件的通用交互需求。常见应用包括焦点控制、拖拽交互、懒加载等,是复杂交互场景下的重要工具。
注册指令:全局注册 和 局部注册 (directive)
应用场景:
- 防抖
- 图片懒加载
- 一键 Copy 功能
- 权限校验
8.生命周期
生命周期 | 描述 |
---|---|
beforeCreate | 组件实例被创建之初,组件的属性生效之前 |
created | 组件实例已经完全创建,data 数据初始化,属性也绑定,但真实的 DOM 还没生成,$el( 虚拟 DOM ) 还不可用 |
beforeMount | 在挂在之前被调用,$el 初始化:相关的 render 函数首次被调用 |
mounted | el 被新创建的 vm.$el 替换,完成挂载 |
beforeUpdate | 组件数据更新之前调用,发生在虚拟 DOM 打补丁之前 |
update | 组件数据更新之后 |
activited | keep-alive 专属,组件被激活时调用 |
deactivated | keep-alive 专属,组件被销毁时调用 |
beforeDestory | 组件销毁前调用 |
destoryed | 组件销毁后调用 |
初始化顺序: props、methods、data
父子组件的加载的生命周期:父 beforeCreate => 父 create => 父 beforeMount => 子 beforeCreate => 子 create => 子 beforeMount => 子 mounted => 父 mounted
9. 在哪个生命周期内调用异步请求
created、beforeMount 、mounted
推荐 created 中请求异步
- 能更快的获取到服务端数据,减少页面的 loading 时间
- ssr 不支持 beforeMount 、mounted 钩子函数,所以放在 created 中有助于一致性
- 请求在 mounted 中有可能会导致页面闪动
10. SSR
SSR 服务端渲染,在服务端将标签渲染成整个 html 片段,直接返回给客户端
优点:
1). 更好的SEO( 搜索引擎优化 )
2). 首屏加载更快,不需要下载 Vue 编译后的 js 和 css
缺点:
1). 更多的开发条件限制:例如服务端渲染只支持 beforeCreate 和 created 这两个钩子函数
2). 更多的服务器负载: 在服务器中渲染完整的应用程序,显然会比仅仅提供静态文件的 server 更加大量占用 CPU 资源
11.说说你对 VUE 的理解?
Vue.js 是一款渐进式 JavaScript 框架,其核心理念是“逐步增强”,允许开发者从简单页面到复杂单页应用(SPA)平滑升级,无需全盘重构。
- 响应式系统:基于 Proxy (Vue3.x)实现数据双向绑定,自动追踪依赖并高效更新视图。例如,通过 ref 和 reactive 管理状态,结合编译优化(如静态提升、补丁标记)、运行时性能提高显著
- 模板语法:接近原生 HTML 的模板语法,降低学习门槛,适合传统 Web 开发者快速上手
- 渐进式迁移:支持按需引入功能模块(如路由、状态管理),避免一次性技术债务
Vue 的核心特征:
- 数据驱动 (MVVM)
- 组件化:把图形、非图形的各种逻辑均抽象为一个统一的概念(组件)来实现开发的模式
- 指令系统
组件化的优势:
- 降低整个系统的耦合度,在保持接口不变的情况下,我们可以替换不同的组件快速完成需求,例如输入框,我们可以替换为日历、时间、范围等组件作具体的实现
- 调试方便,由于整个系统是通过组件组合起来的,在出现问题的时候,可以排除法直接移除组件、或者根据报错的组件快速定位问题,是因为每个组件之间低耦合,职责单一,所以逻辑分析会比整个系统要简单
- 提高可维护性,由于每个组件的职责单一,并且组件在系统是被复用的,所以对代码进行优化可获得系统的整体升级
指令系统:
指令是带有 v- 前缀的特殊属性作用:当表达式的值改变时,将其产生的连带影响,响应式的作用于 DOM 。v-if 、v-for、v-bind、v-model
12. Vue 和 React 对比
相同点:
- 都有组件化思想
- 都支持服务器端渲染
- 都有 Virtual DOM (虚拟 DOM)
- 数据驱动视图
- 都有支持 native 的方案:Vue 的 weex、React 的React native
- 都有自己的构建工具:Vue 的 vue-cli、React 的 create React App
区别
- 数据变化的实现原理不同。React 使用的是不可变数据,而 Vue 使用的是可变数据
- 组件化通信的不同。React 中我们通过使用回调函数进行通信,而 Vue 中子组件向父组件传递消息方式有两种方式:事件和回调函
- diff 算法不同。React 主要使用 diff 队列保存需要更新哪些 DOM,得到 patch 树,再统一操作批量更新 DOM。Vue 使用双指针,边对比,边更新 DOM
维度 | Vue 优势 | React 优势 |
---|---|---|
开发效率 | 开箱即用,适合快速迭代 | 灵活架构,适合长期维护与扩展 |
性能 | 中小型应用更优,响应式更新精准 | 复杂应用优化潜力大,并发模式提升流畅度 |
学习成本 | 低门槛,文档友好 | 高上限,适合技术深度探索 |
生态 | 国内支持强,工具链完善 | 全球化生态,企业级方案成熟 |
13.说说对双向绑定的理解
MVVM
VM (ViewModel)的主要职责是:
- 数据变化后更新视图
- 视图变化后更新数据
流程:
- new Vue() 首先执行初始化,对 data 执行响应化处理,这个过程发生在 Observe 中
- 同时对模版执行编译,找到其中动态绑定的数据,从 data 中初始化视图,这个过程发生在 Complie 中
- 同时定义一个更新函数和 Watcher ,将来对应数据变化时 Watcher 会调用更新函数
- 由于 data 的某个 key 在视图中可能出现多次,所以每个 key 都需要一个管家 Dep 来管理多个 Watcher
- 将来 data 中的数据一旦发生变化,会首先找到对应的 Dep ,通知所有的 Watcher 执行更新函数
14.Vue的性能优化有哪些?
1. 编码阶段
1.1 减少响应式依赖
- 精简 data 数据:仅将需要响应式变化的变量放入 data,避免无意义的数据劫持(Object.defineProperty 或 Proxy)带来的性能损耗
- 冻结静态数据:对纯展示的长列表使用 Object.freeze()冻结,跳过响应式处理
1.2 条件渲染与列表优化
- v-if 与 v-show 选择
- v-for 优化:
- 始终为列表设置唯一 key,帮助 Vue 高效复用 DOM
- 避免 v-for 与 v-if 同级使用,优先用计算属性过滤数据
- 大数据列表使用 虚拟滚动(如 vue-vurtual-scroller),仅渲染可视区域元素,内存占用降低 50%
** 1.3计算属性与事件优化**
- computed 缓存:替代方法(methods)处理复杂逻辑,避免重复计算。
- 事件代理:在 v-for 中绑定事件是,使用事件代理减少事件监听数量
2.组件与架构优化
2.1组件懒加载
- 路由懒加载
- 组件异步加载
2.2 缓存与复用
- keep-alive 缓存组件
- v-once 静态内容:标记永不变化的静态部分,跳过后续 Diff 过程
2.3 轻量化组件
- 函数式组件:无状态组件标记为函数式(functional:true),减少实例化开销
- 逻辑复用
3打包阶段
3.1代码压缩与分割
- Gzip 压缩:通过 compression-webpack-plugin压缩代码
- 代码分割(code Splitting):利用 SplitChunksPlugin 抽离公共模块(lodash、vue)
- Tree Shaking:移除未使用代码(Vue 3 默认支持)
3.2资源优化
- CDN 加速
- 图片懒加载
- (图片)webP 格式
4.用户体验
- 骨架图: 首屏加载期间展示占位图,降低用户等待焦虑;
- PWA (渐进式 Web 应用):通过 Service Worker 实现离线访问和资源加载;
- 预渲染 (Prerendering):静态页面预生成 HTML,加速 SEO 和首屏加载;
15.SPA 首屏加载速度慢怎么解决
先计算 通过 DOMContentLoad 或者 performance 计算出首屏时间
javascript">// 方案一:
document.addEventListener('DOMContentLoaded', (event) => {console.log('first contentful painting');
});
// 方案二:
performance.getEntriesByName("first-contentful-paint")[0].startTime// performance.getEntriesByName("first-contentful-paint")[0]
// 会返回一个 PerformancePaintTiming的实例,结构如下:
{name: "first-contentful-paint",entryType: "paint",startTime: 507.80000002123415,duration: 0,
};
加载慢的原因:
- 网络延迟问题
- 资源文件体积是否过大
- 资源是否重复发送请求去加载了
- 加载脚本的时候,渲染内容堵塞了
解决方案:
几种常用的 SPA 首屏优化方式:
- 减小入口文件体积
- 静态资源本地缓存:强缓存、协商缓存、Service Worker 离线缓存
- UI 架构按需加载:
- 图片资源的压缩:矢量字体、精灵图
- 组件重新打包
- 开启 GZip 压缩:使用 compression-webpack-plugin
- 使用 SSR
减小入口文件体积:
- 路由懒加载,配置路由采用动态路由
- externals 加载外部 CDN 资源
16.Vue 实例挂载的过程中发生了什么?
- new Vue 的时候会调用 _init 方法
* 定义 $ set 、$ get、 $ delete 、$watch 等方法
* 定义 $ on、$ off 、$ emit 、等事件
* 定义 _update、$ forceUpdate、$ destroy 等生命周期 - 调用 $ mount 进行页面的挂载
- 挂载的时候主要通过 mountComponent 方法
- 定义 updateComponment 更新函数
- 执行 render 生成虚拟 DOm
- _update 将虚拟 DOM 生成真实 DOM 结构,并且渲染到页面中
17. v-if 和 v-for 不建议一起用
在 Vue 2.0 中 v-for 的优先级比 v-if 高 ,作用域同一个元素上,带来性能的浪费(每次渲染都会先循环在进行条件判断)
单在 Vue 3.0 中 v-if 的优先级永远比 v-for 高
18. Vue 中给对象添加新属性界面不刷新?
- Vue.set( target: Object | Array ,proprertyName/index : String| number, value: Number)
- Object.assign()
- $forcecUpdated()
小结: - 如果为对象添加少量的新属性,可以直接采用 Vue.set()
- 如果需要为新对象添加大量的新属性,则通过 Object.assign() 创建对象
- 如果实在是不知道怎么操作时候,可采用 $forceUpdate() 进行强制刷新(不建议)
18.Vue 中组件和插件有什么区别
插件:
- 添加全局方法或者属性。如:vue-custom-element
- 添加全局资源:指令/ 过滤器 / 过渡 等。如 vue-touch
- 通过全局混入来添加一些组件选项。如 vue-router
- 添加 Vue 实例方法,通过把它们添加到 Vue.prototype 上实现
- 一个库,提供自己的 API,同时提供上面提到的一个或者多个功能。如 vue-router
组件 | 插件 | |
---|---|---|
编写形式 | vue 单文件,每一个 .vue 文件都可以看成一个组件 | vue 插件的实现应该暴露一个 install 方法,这个方法的第一个参数是 Vue 构造器,第二个参数是一个可选的选项对象 |
注册形式 | 全局注册和局部注册 | 通过 Vue.use() 注册 |
使用场景 | 用来构成你的 App 的业务模块 | 对 Vue 的功能的增强或者补充 |
19. 说说对 vue 的 mixin 的理解,有什么应用场景?
局部混入 和 全局混入
合并策略:
- 替换型:props、methods、inject、computed
- 合并型:data,通过 set 方法进行合并和重新赋值
- 队列型:全部生命周期和 watch,原理是将函数存入一个数组,然后正序遍历执行
- 叠加型:component、directives、filters 通过原型链进行层层的叠加
20. SSR 解决了什么问题?
解决:
- 利于 SEO
- 首屏呈现渲染,提高首屏加载速度
缺点: - 复杂度:整个项目的复杂度
- 库的支持性,代码兼容
- 性能问题:内存消耗变大、缓存、降级
- 服务器负载变大
过程:
- 使用 ssr 不存在单例模式,每次用户请求都会创建一个新的 vue 实例
- 使用 ssr 需要实现服务端首屏渲染和客户端激活
- 服务端异步获取数据 asyncData 可以分为首屏异步获取和切换组件获取
- 首屏异步获取数据,在服务端预渲染的时候就应该已经完成
- 切换组件通过 mixin 混入,在 beforeMount 钩子完成数据获取
21.Vue.observable
Vue.observable 是 Vue.js 提供的一个工具函数,将普通 JavaScript 对象转换为响应式对象。其核心作用是通过数据劫持(Vue 2.x 使用 Object.defineProperty,Vue 3.x 使用 Proxy)实现状态变化的自动追踪和视图更新
// Vue 2.x
const state = Vue.observable({ count: 0 });
state.count++; // 直接修改原对象// Vue 3.x
import { reactive } from 'vue';
const state = reactive({ count: 0 }); // 等同于 Vue.observable
22.Vue 中 key 的原理
使用场景:
- 当我们在使用 v-for 时,需要给单元加上 key
- 用 +new Date() 生成的时间戳作为 key ,手动强制触发重新渲染
key的作用:
key 是给每一个 vnode 的 唯一 id,也是 diff 的一种优化策略,可以根据 key,更准确,更快的找到对应的 vnode 节点
23.Vue 项目中有封装过 axios ?
axios 是一个轻量的 HTTP 客户端,基于 XMLHTTPRequest 服务来执行 HTTP 请求
特性:
- 从浏览器中创建 XMLHttpRequest
- 从 node.js 创建 http 请求
- 支持 Promise APL
- 拦截请求和响应
- 转换请求数据和响应数据
- 取消请求
- 自动转换 JSON 数据
- 客户端支持 防御 XSRF
如何封装:
- 封装的同时,你需要和后端协商好一些约定,请求头、状态码、请求超时时间…
- 设置接口请求前缀:根据开发、测试、生产环境的不同,前缀需要加以区分
- 请求头:来实现一些具体的业务,也必须携带一些参数才可以请求
- 状态码:根据不同接口返回的不同 status,来执行不同的业务
- 请求方法:根据 get 、post 等方法进行一个再次封装
- 请求拦截器:更加请求的请求头设定,来决定哪些请求可以访问
- 响应拦截器:根据后端返回的状态码进行判定执行不同的业务
24. Vue 项目的目录结构
基本原则:
- 文件夹和文件夹内部语义一致性
- 单一入口/出口
- 就近原则,紧耦合的文件应该放到一起,且应以相对路径引用
- 公共的文件应该以绝对路径的方式从根目录引用
- ./src 外的文件不应该被引入
25. vue 项目如何部署,404 问题
为什么在 history 模式下有问题?没有刷新
为什么在 hash 模式下没有问题?
解决方案: nginx 重定向