Vue2 和 Vue3 的双向绑定核心原理完全不同,Vue3 是对 Vue2 的性能优化和逻辑重构,核心差异的一句话总结:Vue2 用「Object.defineProperty 劫持对象属性」,Vue3 用「Proxy 代理整个对象」,且均配合「发布 - 订阅模式」完成数据响应。
一、核心差异对比
| 对比维度 | Vue2(双向绑定) | Vue3(双向绑定) |
|---|---|---|
| 核心 API | Object.defineProperty | Proxy(ES6 新增) |
| 劫持粒度 | 劫持单个对象属性(需遍历对象所有属性) | 代理整个对象 / 数组(无需遍历,直接代理顶层) |
| 响应式范围 | 1. 新增对象属性 / 数组元素(如 arr.push)不响应,需用 Vue.set/this.$set;2. 只能劫持对象,无法直接代理数组 | 1. 新增 / 删除属性、数组操作(push/pop/splice 等)自动响应,无需额外 API;2. 天然支持对象、数组、Map、Set 等复杂数据类型 |
| 性能表现 | 初始化时需遍历所有属性劫持,大对象 / 数组性能较差;删除属性无法监听 | 懒代理(访问属性时才劫持),初始化性能大幅提升;监听范围更全,无遗漏 |
| 语法写法(v-model 双向绑定) | 1. 普通表单:v-model="msg"(本质是 :value + @input 语法糖);2. 组件通信:需用 props 接收 + $emit('input') 触发,或 .sync 修饰符 | 1. 普通表单:用法不变(v-model="msg"),底层逻辑适配 Proxy;2. 组件通信:简化为 v-model 直接绑定,无需 .sync,可通过 emits 声明事件(更规范) |
| 局限性 | 1. 无法监听数组索引修改、长度变化;2. 无法监听对象新增 / 删除属性;3. 对嵌套对象需递归劫持,性能开销大 | 1. 解决 Vue2 所有局限性;2. 嵌套对象自动递归代理(懒加载,访问时才处理);3. 支持监听集合类型(Map/Set) |
二、核心原理拆解
1. Vue2 双向绑定原理(Object.defineProperty)
核心逻辑:逐个劫持数据对象的属性,给每个属性设置 getter(读取数据时触发,收集依赖)和 setter(修改数据时触发,通知视图更新)。
- 简单示例(模拟核心逻辑):
function defineReactive(obj, key, val) {
// 递归劫持嵌套对象
Object.keys(val).forEach(k => defineReactive(val, k, val[k]))
// 劫持单个属性
Object.defineProperty(obj, key, {
get() {
console.log('读取数据,收集依赖');
return val; // 收集当前组件的依赖(发布-订阅模式的「订阅」)
},
set(newVal) {
if (newVal !== val) {
val = newVal;
console.log('修改数据,通知视图更新'); // 触发依赖更新(发布-订阅模式的「发布」)
}
}
})
}
- 核心问题:初始化时要遍历对象所有属性,嵌套越深,性能越差;新增 / 删除属性时,没有
getter/setter,无法触发响应。
2. Vue3 双向绑定原理(Proxy)
核心逻辑:直接代理整个数据对象,无需逐个劫持属性,Proxy 能拦截对象的所有操作(读取、修改、新增、删除、数组操作等),配合 Reflect 统一操作对象,效率更高。
- 简单示例(模拟核心逻辑):
function createReactive(obj) {
// 代理整个对象,无需遍历
return new Proxy(obj, {
// 拦截「读取属性」(包括嵌套属性)
get(target, key, receiver) {
console.log('读取数据,收集依赖');
// 递归代理嵌套对象(懒加载,访问时才代理)
const res = Reflect.get(target, key, receiver);
return typeof res === 'object' ? createReactive(res) : res;
},
// 拦截「修改/新增属性」
set(target, key, value, receiver) {
console.log('修改/新增数据,通知视图更新');
return Reflect.set(target, key, value, receiver);
},
// 拦截「删除属性」
deleteProperty(target, key) {
console.log('删除数据,通知视图更新');
return Reflect.deleteProperty(target, key);
}
})
}
- 核心优势:无需提前遍历属性,初始化性能翻倍;能拦截所有操作,解决 Vue2 的所有局限性,且对复杂数据类型支持更友好。
三、实际开发差异(必用场景)
- 数据响应式写法:
- Vue2:需用
data()返回对象,Vue 内部自动劫持属性:export default { data() { return { msg: 'vue2', list: [1,2,3] } }, methods: { addItem() { // 数组新增元素,需用 this.$set 才能响应 this.$set(this.list, 3, 4); } } } - Vue3:用
ref(基础类型)/reactive(引用类型)创建响应式数据,无需额外 API:import { ref, reactive } from 'vue'; export default { setup() { const msg = ref('vue3'); // 基础类型用 ref const list = reactive([1,2,3]); // 引用类型用 reactive const addItem = () => { list.push(4); // 直接操作,自动响应,无需额外 API }; return { msg, list, addItem }; } }
- Vue2:需用
- 组件间双向绑定:
- Vue2:要么用
props + $emit,要么用.sync修饰符(语法繁琐); - Vue3:直接用
v-model绑定,配合emits声明事件,更简洁规范:<!-- 父组件 --> <Child v-model="msg" /> <!-- 子组件 --> <template><input @input="handleInput" :value="modelValue" /></template> <script setup> import { defineProps, defineEmits } from 'vue'; const props = defineProps(['modelValue']); // 固定接收 modelValue const emit = defineEmits(['update:modelValue']); // 固定触发事件 const handleInput = (e) => { emit('update:modelValue', e.target.value); // 触发父组件数据更新 }; </script>
- Vue2:要么用
2226

被折叠的 条评论
为什么被折叠?



