Vue数据双向绑定主要指的是:数据变化更新视图,视图变化更新数据。
具体步骤:
v-model结合了v-bind和@input两个指令的功能来实现MVVM数据的双向绑定,其原理是采用数据劫持结合发布者-订阅模式的方式。
- 实现一个监听器 Observer:对数据对象进行遍历,包括子属性对象的属性,利用 Object.defineProperty() 对属性都加上 setter 和 getter。这样的话,给这个对象的某个值赋值,就会触发 setter,那么就能监听到了数据变化。
- 实现一个解析器 Compile:解析 Vue 模板指令,将模板中的变量都替换成数据,然后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数,添加监听数据的订阅者,一旦数据有变动,收到通知,调用更新函数进行数据更新。
- 实现一个订阅者 Watcher:Watcher 订阅者是 Observer 和 Compile 之间通信的桥梁 ,主要的任务是订阅 Observer 中的属性值变化的消息,当收到属性值变化的消息时,触发解析器 Compile 中对应的更新函数。
- 实现一个订阅器 Dep:订阅器采用 发布-订阅 设计模式,用来收集订阅者 Watcher,对监听器 Observer 和 订阅者 Watcher 进行统一管理。
var vm = new Vue({
data: {
obj: {
a: 1
}
},
created: function () {
console.log(this.obj);
}
});
属性a有两个相对应的get和set方法,为什么会多出这两个方法呢?因为vue是通过Object.defineProperty()来实现数据劫持的。Object.defineProperty()可以来控制一个对象属性的一些特有操作,比如读写权、是否可以枚举(是否可以被遍历)。
Vue3.x为什么要用Proxy来代替Object.defineProperty?
Proxy:代理对象是用于定义基本操作的自定义行为(例如,属性查找,赋值,枚举,函数调用等)。
换句话说,我们可以说代理对象是我们的目标对象的包装器,我们可以在其中操纵其属性并阻止对它的直接访问。
proxy会在目标对象之前架设一层’拦截’,外界对该对象的访问,都必须先通过这层拦截,因此可以对外界的访问进行过滤和改写。
在vue2中,通过递归遍历data中的所有的property,并使用Object.defineProperty把property全部转为getter/setter,在getter中做数据依赖收集处理,在setter中监听数据的变化,并通知订阅当前数据的地方,但由于JavaScript的限制,这种实现方式有几个问题:
- 无法检测对象属性的添加和移除,需要通过Vue.set/Vue.delete来保证响应系统的运行符合预期。
- 无法监控到数组下标及数组长度的变化,当直接通过数组的下标给数组设置值或者改变数组长度时,不能实时响应
- 性能问题,当data中数据比较多且层级很深的时候,因为要遍历data中所有数据并给其设置响应式,会导致性能下降。
而proxy恰恰解决了上述问题。vue3中,使用proxy替换了原来遍历对象使用Object.defineProperty方法给属性添加set/get。
vue的核心能力之一是监听用户定义的状态变化并响应式刷新DOM。
vue2是通过替换状态对象属性的getter和setter来实现的,vue3则通过proxy进行。
改为proxy后,可以突破vue当前的限制,解决之前vue2的无法监听新增属性,还能提供更好的性能表现。
Object.defineProperty只能遍历对象属性进行劫持。
function observe(obj) {
if (typeof obj !== 'object' || obj == null) {
return
}
Object.keys(obj).forEach(key => {
defineReactive(obj, key, obj[key])
})
}
Proxy直接可以劫持整个对象,并返回一个新对象,我们可以只操作新的对象达到响应式目的。
function reactive(obj) {
if (typeof obj !== 'object' && obj != null) {
return obj
}
// Proxy相当于在对象外层加拦截
const observed = new Proxy(obj, {
get(target, key, receiver) {
const res = Reflect.get(target, key, receiver)
console.log(`获取${key}:${res}`)
return res
},
set(target, key, value, receiver) {
const res = Reflect.set(target, key, value, receiver)
console.log(`设置${key}:${value}`)
return res
},
deleteProperty(target, key) {
const res = Reflect.deleteProperty(target, key)
console.log(`删除${key}:${res}`)
return res
}
})
return observed
}
Proxy可以直接监听数组的变化(push、shift、splice),此外Proxy 不兼容IE,也没有 polyfill, defineProperty 能支持到IE9。