总结一下:
实现数据的双向绑定,首先要对数据进行劫持监听,所以我们需要设置一个监听器Observer,用来监听所有属性。如果属性发上变化了,就需要告诉订阅者Watcher看是否需要更新。因为订阅者是有很多个,所以我们需要有一个消息订阅器Dep来专门收集这些订阅者,然后在监听器Observer和订阅者Watcher之间进行统一管理的。
watcher:订阅者 watcher类用于定义一个订阅者,构造函数传入一个参数为vue对象,一个为data的属性(是node节点的v-model或v-on:click等指令的属性值。如v-model=“name”,exp就是name;),一个回调函数cb。这个回调函数cb就是发布者在notify时,订阅者会执行的回调函数。
初始化时要将自己添加到订阅器Dep里,核心:添加Dep.target为自己(this),判断if(Dep.target)是否要添加订阅者.dep.addsubs(Dep.target)之后让Dep.target赋值为null,添加成功后再将其去掉
let value = this.vm.data[this.exp] // 强制执行监听器里的get函数
在这个过程中会对 vm 上的数据访问,其实就是为了触发数据对象的getter。
每个对象值的 getter都持有一个 dep,在触发 getter 的时候会调用 dep.depend() 方法,
也就会执行this.addSub(Dep.target),即把当前的 watcher 订阅到这个数据持有的 dep 的 subs 中,
这个目的是为后续数据变化时候能通知到哪些 subs 做准备。
Dep:既是订阅器核心,也是发布者:subs[]收集订阅者,notify通知订阅者数据更新了,其中subs和notify都是订阅者watcher里的属性值
observer:监听器 观察者(关联发布者和订阅者)核心:用Object.defineProperty监听所有属性,发生变化告诉watcher
解析器:Compile 解析dom节点。获取到dom元素,然后对含有dom元素上含有指令的节点进行处理,因此这个环节需要对dom操作比较频繁,所有可以先建一个fragment片段,将需要解析的dom节点存入fragment片段里再进行处理:
获取到最外层节点后,调用compileElement函数,对所有子节点进行判断,如果节点是文本节点且匹配{{}}这种形式指令的节点就开始进行编译处理,编译处理首先需要初始化视图数据,
- Vue双向绑定原理的时候,发现订阅器Dep是可以直接调用他自己的target的属性,但是class本质是一个函数,为此确认了一下
//为啥Dep类本质是个函数,可以直接调用属性?
class HanClass{
constructor(){
this.name = 'xixixix'
}
}
function HanFn(){
this.name = 'gogogo'
}
console.log(HanFn.name);//函数也是可以直接调用自己的属性的,无需实例化!
console.log(HanClass.name);
结论:函数是可以直接调用到自己本身的方法的
- 如何解释Vue的双向绑定原理?
双向绑定原理是利用发布订阅模式结合数据劫持来实现的,发布订阅模式是指我们有一个监听器observer,一个订阅器Dep,
一个订阅者watcher。
我们把data里的属性通过监听器observer全部绑定了数据劫持,数据劫持的get方法中判断是否要添加watcher到订阅器数组subs里面,也就是在我们初始化的data的时候,这个data的属性其实就是一个订阅者watcher,
set方法执行了订阅器的notify方法通知watcher,
他在初始化的时候construtor传入的vm为Vue实例,exp为这个data的属性,最后一句this.value = this.get()强制执行了get方法,
而此时get方法里Dep.target = this让订阅器的target为自己,然后将为自己添加到订阅器Dep的订阅者数组subs里,执行完之后让
Dep.target = null即可
源码:
HTML:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<h1 id="name"></h1>
<input type="text">
<input type="button" value="改变data内容" onclick="changeInput()">
<script src="./observer.js"></script>
<script src="./watcher.js"></script>
<script>
function myVue(data, el, exp) {
console.log('进入了myVue......');
this.data = data;
observable(data); //将数据变的可观测
el.innerHTML = this.data[exp]; // 初始化模板数据的值
new Watcher(this, exp, function (value) {//在这个函数里只执行一次,但是此时已经添加到subs数组里了,里面的这个回调函数非常关键!!!因为是引用类型,所以notify每次去updata的时候,其实也执行了这段代码,所以起到了修改innerHTML的作用!!!
el.innerHTML = value;
console.log('进入了Watcher类.....');
});
console.log('进入了myVue函数.....');
return this;
}
var ele = document.querySelector('#name');
var input = document.querySelector('input');
var myVue = new myVue({
name: 'hello world'
}, ele, 'name');
//改变输入框内容
input.oninput = function (e) {
myVue.data.name = e.target.value
}
//改变data内容
function changeInput() {
myVue.data.name = "难凉热血"
}
</script>
</body>
</html>
observer.js:
//observer
/**
* 把一个对象的每一项都转化成可观测对象
* @param { Object } obj 对象
*/
function observable (obj) {
if (!obj || typeof obj !== 'object') {
return;
}
let keys = Object.keys(obj);
keys.forEach((key) =>{
defineReactive(obj,key,obj[key])
})
return obj;
}
/**
* 使一个对象转化成可观测对象
* @param { Object } obj 对象
* @param { String } key 对象的key
* @param { Any } val 对象的某个key的值
*/
function defineReactive (obj,key,val) {
let dep = new Dep();
Object.defineProperty(obj, key, {
get(){
console.log('Dep.target。。。。。。', Dep.target);
dep.depend();
console.log(`${key}属性被读取了`);
return val;
},
set(newVal){
val = newVal;
console.log(`${key}属性被修改了`);
dep.notify() //数据变化通知所有订阅者
}
})
}
class Dep {
constructor(){
this.subs = []
}
//增加订阅者
addSub(sub){
this.subs.push(sub);
}
//判断是否增加订阅者
depend () {
if (Dep.target) {
this.addSub(Dep.target)
}
}
//通知订阅者更新
notify(){
this.subs.forEach((sub) =>{
console.log('此时的sub....',sub);
sub.update()
})
}
}
// Dep.target = null;
watcher.js
//watcher
class Watcher {
constructor(vm, exp, cb) {
this.vm = vm;
this.exp = exp;
this.cb = cb;
this.value = this.get(); // 将自己添加到订阅器的操作
}
get() {
Dep.target = this; // 缓存自己
let value = this.vm.data[this.exp] // 强制执行监听器里的get函数
Dep.target = null; // 释放自己
return value;
}
update() {
let value = this.vm.data[this.exp];
let oldVal = this.value;
if (value !== oldVal) {
console.log('sub数组的订阅者Watcher实例的updata方法......');
this.value = value;
this.cb.call(this.vm, value, oldVal);
}
}
}
参考网址
- https://www.cnblogs.com/canfoo/p/6891868.html
- https://www.cnblogs.com/wangjiachen666/p/9883916.html(强烈推荐,写的目前看过最好的)