vue3的API和数据响应式的变化
面试题1:为什么vue3中去掉了vue构造函数?
vue2的全局构造函数带来了诸多问题:
1.调用构造函数的静态方法会对所有vue应用生效,不利于隔离不同应用
2.vue2的构造函数集成了太多功能,不利于treeshaking,vue3把这些功能使用普通函数导出,能够充分利用treeshaking优化打包体积
3.vue2没有把组件实例和vue应用两个概念区分开,在vue2中,通过newVue创建的对象,既是一个vue应用,同时又是一个特殊的vue组件。vue3中,把两个概念区别开来,通过createApp创建的对象,是一个vue应用,它内部提供的方法是针对整个应用的,而不再是一个特殊的组件。
面试题2:谈谈你对vue3数据响应式的理解
vue3不再使用object.defineProperty的方式定义完成数据响应式,而是使用Proxy。
除了Proxy本身效率比object.defineProperty更高之外,由于不必递归遍历所有属性,而是直接得到一个Proxy。所以在
vue3中,对数据的访问是动态的,当访问某个属性的时候,再动态的获取和设置,这就极大的提升了在组件初始阶段的效
率。
同时,由于Proxy可以监控到成员的新增和删除,因此,在vue3中新增成员、删除成员、索引访问等均可以触发重新渲染,
而这些在vue2中是难以做到的。
为什么vue3中去掉了vue构造函数?
Vue 3 移除传统的 Vue 构造函数并改用 createApp 工厂函数,这一设计调整主要基于以下核心原因:
1. 模块化与 Tree Shaking 支持
Vue 3 将核心功能拆分为独立模块(如 reactivity、runtime-core),通过工厂函数 createApp 按需加载,避免未使用代码被打包。这种设计显著减少了生产环境体积(例如,Vue 3 核心包仅约 10 KB,而 Vue 2 约 20 KB)。
• 对比:Vue 2 的全局构造函数会强制引入所有功能(如 Vue.nextTick、Vue.set),即使未使用也无法剔除。
2. 多应用实例隔离
Vue 3 允许创建多个独立的应用实例(通过多次调用 createApp()),每个实例拥有独立的全局配置(如组件、指令、插件),避免污染全局状态。
• 示例:
// 创建两个独立应用实例
const app1 = createApp(App1);
app1.component('MyButton', Button1); // 仅 app1 可用
const app2 = createApp(App2);
app2.component('MyButton', Button2); // 仅 app2 可用
• 解决痛点:Vue 2 的全局构造函数导致多个应用共享同一配置,难以实现微前端或插件隔离。
3. 配置与生命周期的明确性
通过工厂函数返回的实例对象(如 app),所有 API 显式挂载到实例上(如 app.use()、app.mount()),而非全局构造函数。这种设计:
• 避免副作用:例如,Vue 2 中 Vue.use(plugin) 会影响所有后续实例,而 Vue 3 的 app.use(plugin) 仅影响当前实例。
• 生命周期更清晰:实例的挂载(mount)与卸载(unmount)行为更可控,适合动态应用场景(如按需加载组件)。
4. TypeScript 类型支持优化
工厂函数返回的实例类型明确,结合 Composition API 的按需导入,提供了更完善的类型推导和 IDE 支持。
• 示例:
import { createApp, ref } from 'vue'; // 按需导入,类型明确
const app = createApp({ /* ... */ });
app.mount('#app'); // 类型检查 mount 方法的参数合法性
5. 响应式系统的解耦
Vue 3 的响应式模块(reactivity)独立于核心框架,可通过工厂函数灵活集成。这种解耦:
• 支持非 DOM 环境:例如,在服务端渲染(SSR)或 NativeScript 中仅使用响应式模块。
• 简化测试:开发者可单独测试响应式逻辑,无需依赖完整 Vue 实例。
总结:Vue 3 设计哲学的升级
移除构造函数是 Vue 3 整体架构升级的一部分,核心目标包括:
- 轻量化:通过 Tree Shaking 减少代码体积。
- 隔离性:支持多实例独立配置。
- 灵活性:模块化设计适应多样化场景。
- 可维护性:明确的 API 边界提升代码可读性。
这一变化使 Vue 3 更适应现代前端工程化需求(如微前端、SSR、TypeScript),同时保持了开发体验的简洁性。
谈谈你对vue3数据响应式的理解
Vue 3 的数据响应式原理是其核心机制之一,通过 Proxy 代理与 Reflect 反射的协同实现了更高效、更灵活的数据追踪。以下是其核心原理及技术细节:
一、Proxy 代理取代 Object.defineProperty
Vue 3 使用 Proxy 代理整个对象,而非像 Vue 2 那样递归劫持每个属性。
优势对比:
| 特性 | Vue 2 (Object.defineProperty) | Vue 3 (Proxy) |
|---|---|---|
| 动态属性监听 | 需手动调用 Vue.set/Vue.delete | 自动支持新增/删除属性 |
| 数组监听 | 需重写数组方法(如 push) | 直接监听索引变化和 length 修改 |
| 性能 | 初始化时递归劫持,性能损耗大 | 惰性代理,按需触发 |
实现示例:
const obj = { count: 0 };
const proxy = new Proxy(obj, {
get(target, key) {
track(target, key); // 依赖收集
return Reflect.get(target, key);
},
set(target, key, value) {
Reflect.set(target, key, value);
trigger(target, key); // 触发更新
return true;
}
});
通过 Proxy 的 get 和 set 拦截器,Vue 3 能自动追踪所有属性的读写操作。
二、响应式 API:reactive 与 ref
reactive(obj)
• 代理对象或数组,返回一个 Proxy 实例,直接操作属性即可触发响应式。
• 示例:
const state = reactive({ count: 0 });
state.count++; // 触发更新
ref(value)
• 包装基本类型(如字符串、数字)或对象,通过.value访问值。
• 底层通过reactive实现,自动解包嵌套响应式对象。
• 示例:const count = ref(0); count.value++; // 触发更新
三、依赖收集与更新触发
-
依赖收集(Track)
• 在get拦截器中,通过track(target, key)记录当前访问该属性的副作用(如组件渲染函数)。
• 依赖关系存储在WeakMap结构中(targetMap),键为对象,值为属性与依赖的映射。 -
触发更新(Trigger)
• 在set或deleteProperty拦截器中,通过trigger(target, key)通知所有关联的副作用重新执行。
• 更新通过 异步队列 批量处理,减少重复渲染(类似nextTick机制)。
四、性能优化策略
-
编译优化
• 静态提升(Hoist Static):将模板中的静态节点提取为常量,避免重复创建。
• 预字符串化:将连续静态内容合并为字符串,减少虚拟 DOM 节点数量。 -
虚拟 DOM 优化
• Patch Flag:标记动态节点的属性类型(如TEXT或CLASS),仅对比变化部分。
• Block Tree:将动态内容划分为独立区块,跳过静态子树比对。 -
Tree Shaking 支持
• 模块化设计允许构建工具剔除未使用的代码(如未引入的 Composition API 函数)。
五、与 Composition API 的协同
通过 Composition API,响应式系统能更灵活地组织代码:
- 逻辑复用:将响应式数据与副作用封装为自定义 Hook(如
useFetch)。 - 精准控制副作用:使用
watch和watchEffect监听数据变化,实现细粒度更新。
const count = ref(0);
watch(count, (newVal) => console.log('count changed:', newVal));
六、与 Vue 2 的对比总结
| 对比项 | Vue 2 | Vue 3 |
|---|---|---|
| 响应式实现 | Object.defineProperty 递归劫持 | Proxy 代理整个对象 |
| 动态属性支持 | 需手动处理 | 自动监听 |
| 数组处理 | 需重写方法 | 直接监听索引和 length |
| 性能 | 初始化性能较差 | 惰性代理,按需触发,性能更优 |
总结
Vue 3 的响应式系统通过 Proxy 代理、动态依赖收集 和 编译优化,解决了 Vue 2 的局限,显著提升了性能和灵活性。其核心思想是 “按需追踪”,仅在数据被访问时建立依赖关系,而非初始化时全量劫持,这使得 Vue 3 在处理大型应用和复杂数据结构时表现更优。
评论