Vue内部通过Object.defineProperty方法属性拦截的方式,把data对象里每个数据的读写转化成getter/setter,当数据变化时通知视图更新。
所谓MVVM数据双向绑定,即主要是:数据变化更新视图,视图变化更新数据。
我的效果图:
input 里面输入或删除,会同步到h1中
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<script src="know.js"></script>
<title>Document</title>
</head>
<body>
<h1 id="text"></h1>
<input type="text">
<script>
text = document.querySelector("#text");
input = document.querySelector("input");
// 箭头函数 没有 没有prototype(构造函数),故不能 new ,所以 用 function 了
function myVue(data, key, el) {
this.data = data;
// 对data中的每个属性都定义 get,set 方法,
//一旦 data中的属性值发生变化,get、set就能捕捉到,
// 这也就实时监听 data 的变化了,所以能够双向绑定
// 将数据变的可观测
observable(data);
el.innerHTML = this.data[key];
new Watcher(this, key, (val) => {
el.innerHTML = val;
});
return this;
};
var myVue = new myVue({
name: 'HEllo!'
}, 'name', text);
input.oninput = (e) => {
// 触发 set 方法
// M 数据发生 变化 ,V 视图同步改变: M---> V
myVue.data.name = e.target.value;
}
</script>
</body>
</html>
know.js
observable = (obj) => {
if (!obj || typeof obj !== 'object') {
return;
}
// 得到 字典obj的所有 key
keys = Object.keys(obj);
keys.forEach(key => {
//Object.defineProperty方法: 数据的每次读和写能够被我们看的见,
//即我们能够知道数据什么时候被读取了或数据什么时候被改写了,
// 我们将其称为数据变的‘可观测’
defineReactive(obj,key,obj[key])
})
}
defineReactive = (obj, key, value) => {
dev = new Dev();
Object.defineProperty(obj, key, {
get() {
// 属性对象(Dev.target)不为 null,即添加订阅者 Watcher
dev.addWatcher();
// 实时看到 读取值
console.log(`${key} 被读取了!: ${value}`);
return value;
},
set(newVal) {
value = newVal;
console.log(`${key} 被修改了!:新值:${newVal}`)
// oninput事件修改了data的话,就会通知data中的所有属性(Watcher)更新
dev.notify();
}
})
}
// 消息订阅器Dev 来专门收集这些订阅者(Watcher)
class Dev {
constructor() {
// 页面中 所有的属性对象,有update方法
this.subs = []
};
addWatcher() {
if (Dev.target) {
this.subs.push(Dev.target);
}
};
notify() {
this.subs.forEach(sub => {
sub.update();
})
}
}
class Watcher {
constructor(myVue, key, callback) {
this.myVue = myVue;
this.key = key;
this.callback = callback;
// 存放以前的value
this.value = this.get();
};
get() {
Dev.target = this;
// this.myVue.data[this.key]: 触发 get方法:
// 消息订阅器Dev添加当前的class Watcher(Dev.target)
var value = this.myVue.data[this.key];
Dev.target = null;
return value;
};
update() {
// 这就是最新的value
// this.myVue.data[this.key]: 触发 get方法
var newVal = this.myVue.data[this.key];
if (this.value != newVal) {
this.value = newVal;
this.callback(newVal);
}
}
}
Dev.target = null;
总结一下:实现数据的双向绑定,首先要对数据进行劫持监听,所以我们需要设置一个监听器Observer,用来监听所有属性。如果属性发上变化了,就需要告诉订阅者Watcher看是否需要更新。因为订阅者是有很多个,所以我们需要有一个消息订阅器Dev来专门收集这些订阅者,旨在监听器Observer和订阅者Watcher之间进行统一管理。