1. 实现双向绑定的过程理论阐述
如何实现双向绑定,上图的流程显示,
(1)需要用Observer来监听属性的变化
(2)当属性变化时候,需要通知订阅者Watcher是否需要更新,此时订阅者可能具有多个,因此需要订阅器Dep来专门接收这些订阅者,并统一管理
(3)解析器Compile可以对每一个元素节点进行解析和扫描,将相关指令对应初始化成为一个订阅者Watcher,并替换对象中的数据或者函数
(4)订阅者Watcher接收相应的属性变化,更新函数和视图
2.代码展示
展示vue data对象中已经封装set 和get函数:
var vm = new Vue({
data: {
obj: {
a: 1
}
},
created: function () {
console.log(this.obj);
}
});
2.如何实现一个Observer:
Observer是一个数据监听器,其核心就是使用Object.defineProperty().
function monitorData(data, key, val) {
observe(val); // 递归遍历所有子属性
Object.defineProperty(data, key, {
get: function() {
return val;
},
//利用set函数监听data对象
set: function(newVal) {
val = newVal;
console.log('属性' + key + '正在被监听属性值在发生改变:“' + newVal + '”');
}
});
}
function observe(data) {
//判断data是否为object
if (!data || typeof data !== 'object') {
return;
}
//Object.keys(data) 返回一个所有元素为字符串的数组,其元素来自于从给定的object上面可直接枚举的属性。这些属性的顺序与手动遍历该对象属性时的一致。
Object.keys(data).forEach(function(key) {
console.log(key);
monitorData(data, key, data[key]);
});//遍历所有data的对象的属性状态
};
var data = {
data1: {
value: ''
},
data2: ''
};
observe(data);
data.data1.value = '1';
data.data2 = '2';
结果展示:
提要:(1)Object.keys(obj);方法:点击此处查看用法
3.如何实现Dep
需要创建一个可以容纳订阅器Dep的容器,这个容器是list用一个数组表示,将上面的Observer改造一下,植入dep消息订阅器
function defineReactive(data, key, val) {
observe(val); // 递归遍历所有子属性
var dep = new Dep();
console.log(dep);
Object.defineProperty(data, key, {
enumerable: true,
configurable: true,
get: function() {
if(Dep.target) { // 判断是否需要添加订阅者
dep.addSub(Dep.target); // 在这里添加一个订阅者
}
return val;
},
set: function(newVal) {
if(val === newVal) {
return;
}
val = newVal;
console.log('属性' + key + '正在被监听属性值在发生改变:“' + newVal + '”');
dep.notify(); // 如果数据变化,通知所有订阅者
}
});
}
function observe(data) {
if(!data || typeof data !== 'object') {
return;
}
Object.keys(data).forEach(function(key) {
defineReactive(data, key, data[key]);
});
};
//维护订阅器Dep;
function Dep() {
this.subs = [];//数组
}
Dep.prototype = {
addSub: function(sub) {//添加订阅者
this.subs.push(sub);
},
notify: function() {//通知订阅者
this.subs.forEach(function(sub) {
sub.update();
});
}
};
Dep.target = null;
var data = {
data1: {
value: ''
},
data2: ''
};
observe(data);
data.data1.value = '1';
data.data2 = '2';
结果展示:
4.如何实现订阅者Watcher
(1)订阅者Watcher在初始化阶段将自己添加入Dep中,然后同时调用对应的在监听器Observer的Object.defineProperty();方法中的get函数去执行相应的添加订阅者的操作即可。
(2)在Watcher初始化阶段才需要添加订阅者,因此需要用Dep.target缓存下订阅者,添加成功后直接去除也就是Dep.target = null;
function Watcher(vm, exp, cb) {
this.cb = cb;
this.vm = vm;
this.exp = exp;
this.value = this.get(); // 将自己添加到订阅器的操作
}
Watcher.prototype = {
update: function() {
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] // 强制执行监听器里的get函数
Dep.target = null; // 释放自己
return value;
}
};
5.如何实现简易的数据双向传递实例
第一步:关联Observer 和Watcher关联起来
//observer和Watcher关联起来
function TestVue(data, el, exp) {
this.data = data;
observe(data);
el.innerHTML = this.data[exp]; // 初始化模板数据的值
new Watcher(this, exp, function(value) {
el.innerHTML = value;
});
return this;
}
第二步:new 一个TestVue类,来实现双向绑定
var testVue = new TestVue({
data1: "1"
}, elem, 'data1');
window.setTimeout(function() {
console.log('name值改变了');
testVue.data.data1 = '2';
}, 2000);
第三步:在body中写入{{data1}}
<h1 id="name">{{data1}}</h1>
第四步:在script中写入
var elem = document.querySelector('#name');
第五步:呈现完整的代码
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title></title>
<!--<script src="https://cdn.bootcss.com/vue/2.6.10/vue.min.js"></script>-->
</head>
<h1 id="name">{{data1}}</h1>
<body>
</body>
</html>
<script type="text/javascript">
function defineReactive(data, key, val) {
observe(val); // 递归遍历所有子属性
var dep = new Dep();
console.log(dep);
Object.defineProperty(data, key, {
enumerable: true,
configurable: true,
get: function() {
if(Dep.target) { // 判断是否需要添加订阅者
dep.addSub(Dep.target); // 在这里添加一个订阅者
}
return val;
},
set: function(newVal) {
if(val === newVal) {
return;
}
val = newVal;
console.log('属性' + key + '已经被监听了,现在值为:“' + newVal + '”');
dep.notify(); // 如果数据变化,通知所有订阅者
}
});
}
function observe(data) {
if(!data || typeof data !== 'object') {
return;
}
Object.keys(data).forEach(function(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();
});
}
};
Dep.target = null;
function Watcher(vm, exp, cb) {
this.cb = cb;
this.vm = vm;
this.exp = exp;
this.value = this.get(); // 将自己添加到订阅器的操作
console.log(cb);
console.log(vm)
console.log(exp);
console.log(this.get());
}
Watcher.prototype = {
update: function() {
this.run();
},
run: function() {
var value = this.vm.data[this.exp];
var oldVal = this.value;
if(value !== oldVal) {
console.log(value);
this.value = value;
this.cb.call(this.vm, value, oldVal);
}
},
get: function() {
Dep.target = this; // 缓存自己
var value = this.vm.data[this.exp] // 强制执行监听器里的get函数
Dep.target = null; // 释放自己
return value;
}
};
//observer和Watcher关联起来
function TestVue(data, el, exp) {
this.data = data;
observe(data);
el.innerHTML = this.data[exp]; // 初始化模板数据的值
new Watcher(this, exp, function(value) {
el.innerHTML = value;
});
return this;
}
var elem = document.querySelector('#name');
var testVue = new TestVue({
data1: "1"
}, elem, 'data1');
window.setTimeout(function() {
console.log('name值改变了');
testVue.data.data1 = '2';
}, 2000);
</script>
最后结果展示:
未完待续