vue2(vue3没来得及理解)的双向数据绑定是通过Object.defineProperty()来劫持对象属性的set和get事件
关于Object.defineProperty()这个函数的语法
Object.defineProperty(obj, prop, descriptor)
obj:要定义属性的对象。
prop:要定义或修改的属性的名称或 Symbol 。
descriptor:要定义或修改的属性描述符。const object1 = {};
Object.defineProperty(object1, 'property1', {
value: 42,
writable: true
});
object1.property1 = 77;
console.log(object1.property1); // 77developer.mozilla.org/zhCN/docs/W…
var obj = {};
Object.defineProperty(obj, 'name', {
get: function() {
console.log('获取值')
return val;
},
set: function (newVal) {
console.log('设置值')
}
})
obj.name = '数据绑定';//在给obj设置name属性的时候,触发了set这个方法
var val = obj.name; //数据绑定通过Object.defineProperty( )这个方法给obj对象设置了name属性,对其get和set方法进行重写操作,set方法在设置name属性时被触发,get方法在获得name属性时被调用并返回对应的值。
这是双向数据绑定的核心原理:通过Object.defineProperty()实现对属性的劫持,然后同get和set事件达到监听数据变动的目的。
vue双向数据绑定的实现
observer对每个vue中data定义的属性用Object.defineProperty()实现数据劫持,以便利用其中的set和get,然后通知订阅者,订阅者会触发它的update方法,对视图进行更新。
observer的实现
定义一个 defineReactive ,这个方法通过 Object.defineProperty 来实现对对象的响应式化,入参是一个 obj(需要绑定的对象)、key(obj的某一个属性),val(具体的值)。
function defineReactive (obj, key, val) {
Object.defineProperty(obj, key, {
enumerable: true, /* 属性可枚举 */
configurable: true, /* 属性可被修改或删除 */
get: function reactiveGetter () {
return val; /* 实际上会依赖收集 */
},
set: function reactiveSetter (newVal) {
if (newVal === val) return;
console.log(newval);
}
});
}经过 defineReactive 处理以后,我们的 obj 的 key 属性在「读」的时候会触发reactiveGetter 方法,而在该属性被「写」的时候则会触发 reactiveSetter 方法。
然后封装一层 observer 。这个函数传入一个 value(需要响应式化的对象),通过遍历所有属性的方式对该对象的每一个属性都通过 defineReactive 处理。(实际上 observer 会进行递归调用,为了便于理解去掉了递归的过程)
function observer (value) {
if (!value || (typeof value !== 'object')) {
return;
}
Object.keys(value).forEach((key) => {
defineReactive(value, key, value[key]);
});
}Dep的实现
Dep为每个属性添加订阅者,主要作用是用来存放 Watcher 观察者对象
class Dep {
constructor () {
/* 用来存放Watcher对象的数组 */
this.subs = [];
}
/* 在subs中添加一个Watcher对象 */
addSub (sub) {
this.subs.push(sub);
}
/* 通知所有Watcher对象更新视图 */
notify () {
this.subs.forEach((sub) => {
sub.update();
})
}
}
用
addSub方法可以在目前的Dep对象中增加一个Watcher的订阅操作;用
notify方法通知目前Dep对象的subs中的所有Watcher对象触发更新操作。
Watcher观察者
class Watcher {
constructor () {
/* 在new一个Watcher对象时将该对象赋值给Dep.target,在get中会用到 */
Dep.target = this;
}
/* 更新视图的方法 */
update () {
console.log("视图更新啦~");
}
}
Dep.target = null;总结
在 observer 的过程中会注册 get 方法,该方法用来进行依赖收集。在它的闭包中会有一个 Dep 对象,这个对象用来存放 Watcher 对象的实例。依赖收集的过程就是把 Watcher 实例存放到对应的 Dep 对象中去。get 方法可以让当前的 Watcher 对象(Dep.target)存放到它的 subs 中(addSub)方法,在数据变化时,set 会调用 Dep 对象的 notify 方法通知它内部所有的 Watcher 对象进行视图更新。
这是 Object.defineProperty 的 set/get 方法处理的事情,那么「依赖收集」的前提条件还有两个:
触发
get方法;新建一个 Watcher 对象。
这个在 Vue 的构造类中处理。新建一个 Watcher 对象只需要 new 出来,这时候 Dep.target 已经指向了这个 new 出来的 Watcher 对象来。而触发 get 方法也很简单,实际上只要把 render function 进行渲染,那么其中的依赖的对象都会被「读取」,这里我们通过打印来模拟这个过程,读取 test 来触发 get 进行「依赖收集」。
总的来说就是 get 进行「依赖收集」,set 通过观察者来更新视图。