实现mvvm的双向绑定,是采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty()【defineProperty】来劫持各个属性的setter,getter,在数据变动时发布消息给订阅者,触发相应的监听回调。就必须要实现以下几点:
1.监听器Observer:监听变化
2.解析器Compile: 根据指令模板替换数据,以及绑定相应的更新函数
3.Watcher: 订阅并收到每个属性变动的通知,执行指令绑定的相应回调函数
4.mvvm入口函数,整合以上三者
简易版实现:
observer.js
function defineReactive(data, key, val) {
observe(val);//遍历选出可枚举的数据值
var dep = new Dep();
Object.defineProperty(data, key, {
enumerable: true,
configurable: true,
get: function () {
if (Dep.target) {
//dep添加订阅
dep.addSub(Dep.target);
}
return val;
},
set: function (newVal) {
if (val === newVal) {
return;
}
val = newVal;
console.log('属性' + key + '已经被监听了,现在值为:“' + newVal.toString() + '”');
//数据变化则通知订阅者
dep.notify();
}
})
};
Dep.target = null;
function observe(data) {
if (!data || typeof data !== 'object') {
return;
}
Object.keys(data).forEach(function (key) {
//key方法选出可枚举对象
defineReactive(data, key, data[key]);
})
};
function Dep() {
this.subs = [];
}
Dep.prototype = {
addSub: function (sub) {
this.subs.push(sub);
},
notify: function () {
this.subs.forEach(function (sub) {
sub.update();
});
}
};
// var library = {
// book1: {
// name: ''
// },
// book2: ''
// };
// observe(library);
// library.book1.name = '我是第一本书';//属性name已经被监听了,现在值为:“我是第一本书”
// library.book2 = '我是第二本书'; //属性book2已经被监听了,现在值为:“我是第二本书”
watcher
function Watcher(vm, exp, cb) {
this.cb = cb;
this.exp = exp;
this.vm = vm;
this.value = this.get();
}
Watcher.prototype = {
update() {
this.run();
},
run: function () {
var value = this.vm.data[this.exp];
var oldVal = this.value;
if (value !== oldVal) {
this.value = value;
this.cb.call(this.vm, value, oldVal);
}
},
get: function () {
Dep.target = this;
var value = this.vm.data[this.exp]
Dep.target = null;
return value
}
}
index.js
function SelfVue(data, el, exp) {
this.data = data;
observe(data);
el.innerHTML = this.data[exp];
new Watcher(this, exp, function (value) {
el.innerHTML = value;
});
return this
}
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>vueText</title>
</head>
<script src="js/observer.js"></script>
<script src="js/watcher.js"></script>
<script src="js/index.js"></script>
<body>
<h1 id="name">{{name}}</h1>
</body>
<script>
var ele = document.querySelector('#name');
var selfVue = new SelfVue({
name: 'hello world'
}, ele, 'name');
window.setTimeout(function () {
console.log('name值改变了');
selfVue.data.name = 'canfoo';
}, 2000);
</script>
</html>
便于理解执行顺序如下:
首先先走一遍变量:Dep.target = null;以及watcher dep等对象的原型继承
然后开始执行 index.html中获取节点==》创建selfVue
变量值如下
进入observe函数
递归找出可枚举对象
创建dep 进入defineproperty函数
获取初始属性 赋值给html
这里会触发get 并且此时页面会发生改变 “name”==》helloworld
这里的涉及很巧妙因为初始target值为空 故会直接返回val(helloworld)只有当watcher初始化的时候才会压栈
之后按顺序实例化watcher
之后进入watcher中的get
get这个时候target被赋值 不再是空了并且因为get方法需要获取所以进入defineproperty中
把被感染的watcher放入dep中之后再将target其置为空
(平时都是空 只有当watcher初始化时才会变成watcher进而可以对dep进行压栈操作)
都构建完成了 开始更改值 设置一个定时器 给name一个新的值
此时会进入definedeprotype中的set方法
把新值给val 并执行notify函数
此时需要获取data值 再次进入de的get函数中
此时为空会直接返回val (上一步的set已经把他设置成辛德勒)
然后进行比对 之前获取的value是oldvaluehello(get获取pro的get)
然后给一个新的value
然后执行之前存在cb中的index.js的函数 将页面更改
至此一个简易的双向绑定已经完成
简易版总结:::getpro劫持数据 watcher劫持数据(有一个藏watcher监听的过程dep) 当发生改变时通过回调函数修改数据