自己动手写MVVM(一):实现Observe

双向绑定简述

数据绑定,就是把Model与View绑定。当我们使用代码更新Model时,View层自动更新,也称单向绑定。

upload successful

而双向绑定,就是在单向绑定的基础上,用户可通过更新View实现更新Model。

最简单的例子就是网页的输入框Input绑定着模型,可以通过JS改变模型自动修改数据,也可以通过修改输入框的数据自动改变模型。

upload successful

使用VUE实现双向绑定的例子

实现数据绑定的做法有大致如下几种:

框架模式
backbone.js发布者-订阅者模式
angular.js脏值检查
vue.js 2数据劫持
vue.js 3Proxy代理

实现双向绑定大多数是在单向绑定的基础上通过给可编辑元素添加change(input)事件,通过事件触发修改模型。

**发布者-订阅者模式:**一般是通过sub/pub(发布/订阅)的方式对模型和视图进行绑定,可以参考谈谈JavaScript中的双向数据绑定

脏值检查: AngularJs用过使用$digest()来实现脏值检查循环。只有在触发指定事件后才会进入$digest()。同时,使用setInterval()轮询数据变动,也是属于脏值检查的一种。

**数据劫持:**在VueJs2中,使用Object.defineProperty()劫持data属性的settergetter,在数据变动时发布给订阅者,触发相应的监听回调。

**Proxy代理:**在VueJs3中,使用new Proxy(target, handler)对整个obj属性进行代理操作。消除了之前 Vue2.x 中基于 Object.defineProperty 的实现所存在的很多限制:无法监听 属性的添加和删除数组索引和长度的变更,并可以支持 MapSetWeakMapWeakSet

upload successful

实现Observe

使用Object.defineProperty对属性进行劫持

VueJs2中,是通过``Object.defineProperty()`对属性进行劫持,实现监听数据变动。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
function observe(data){
if( !data || typeof data !== "object" ){
return false;
}

// 取出所有属性遍历
Object.keys(data).forEach(function(key) {
let val = data[key];
observe(val);
Object.defineProperty(data, key, {
enumerable: true, // 可枚举
configurable: false, // 不能再配置
get: function() {
return val;
},
set: function(newVal) {
console.log('值变化: ', val, ' -> ', newVal);
val = newVal;
}
});
});
}

var data = {
test: "hello"
};

observe(data)

data.test = "123" // 值变化: 123 -> 123
data.hello = "123" // 无变化,由于hello属性未监听

Object.defineProperty() | MDN

使用Proxy对对象进行代理

todo

数据订阅

目前已经实现了监听data数组的数据的变化,然后我们需要实现一个消息订阅器Dep

实现的思路基本为维护一个数组存储订阅者,当数据发生改变时调用订阅者的update方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
// 观察者模式的实现

function Dep(){
this.subs = [];
}

Dep.prototype = {
// 增加订阅者
add( sub ){
this.subs.push( sub );
},
// 移除订阅者
remove( sub ){
let index = this.subs.indexOf( sub );
if(index>-1){
this.subs.splice( index, 1 );
return true;
}
return false;
},
// 调用订阅者的update方法
notify(){
this.subs.forEach(function( sub ) {
sub.update();
});
}
}

let dep = new Dep();

let subscriber = {
data: "subscriber",
update(){
console.log( `${this.data}: I received Noitfy!` );
}
}


dep.add( subscriber );
dep.notify(); // subscriber: I received Noitfy!
dep.remove( subscriber );
dep.notify(); // undefined

事实上,subscriber就是我们所说的订阅者,在Vue源码中,是使用Watcher进行订阅的。

Watcher的实现将在后续章节中呈现。

目录

参考文章

双向绑定 - 廖雪峰

剖析Vue原理&实现双向绑定MVVM

深入响应式原理 - VueJs官网

初探 Vue3.0 中的一大亮点——Proxy !

观察者模式 vs 发布订阅模式