vue-双向数据绑定原理分析二--Mr.Ember
摘要:双向数据绑定是vue的核心,通过是数据劫持结合发布者-订阅者模式的模式实现的。
上一讲提到了怎么简单的模仿双向数据绑定,下面我们将会分析下vue双向数据绑定源码的简单实现。
实现原理
双向数据绑定是通过数据的劫持结合发布者-订阅者模式的方式实现的。
数据劫持、vue是通过Object.definedProperty()实现数据劫持,其中会有getter()和setter()方法。当读取属性值时,就会触发getter()方法,在view中如果数据发生了变化,就会通过Object.defineProperty()对属性设置一个setter函数,当数据改变了就会触发这个函数;
一. 实现一个监听器observer
observer主要是通过Object.defineProperty()来监听属性的变动,那么将需要observer的数据对象进行递归遍历,包括子属性对象,都加上setter和getter,给这个对象的某个值赋值,就会触发setter,从而监听数据的变化。
Observer.prototype = {
walk: function(data) { //执行函数
var self = this;
// 通过对一个对象进行遍历,对这个对象所有的属性进行监听
Object.keys(data).forEach(function(key) {
self.defineReactive(data, key, data[key]);
});
},
defineReactive: function(data, key, val) {
var dep = new Dep();
// 遍历所有的子属性
var childObj = observe(val);
Object.defineProperty(data, key, {
get: function getter() {
if(Dep.target) {
// 添加一个订阅者
console.log(Dep.target)
dep.addSub(Dep.target);
}
return val;
},
// 如果一个对象属性值发生改变,就会出发setter中的dep.notify(),通知watcher订阅者数据发生变更,执行对应订阅者的更新函数,来更新视图
set: function setter(newVal) {
if(newVal === val) {
return;
}
val = newVal;
// 新的值是object的话,进行监听
childObj = observe(newVal);
dep.notify();
}
})
}
}
监听数据变化之后就是怎么通知订阅者了,所以需要实现一个消息订阅器,维护数组,用来收集订阅者,数据变动触发notify,再调用订阅者的update方法
// 创建Observer实例
function observe(value, vm) {
if(!value || typeof value !== 'object') {
return;
}
return new Observer(value);
}
// 消息订阅器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;
二. 实现一个Compile
compile主要做的是解析模板指令,将模板的变量替换成数据,然后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数,添加监听数据的订阅者,一旦数据有变动,收到通知,更新视图。

因为遍历解析的过程有多次操作DOM节点,为提高性能和效率,会先将节点el转换成文档碎片fragment进行解析编译操作,解析完成,再将fragment添加会真实的DOM节点中
compileElement方法将遍历所有的子节点,进行扫描编译解析,调用对应的指令渲染函数进行数据渲染,并调用对应的指令更新函数进行绑定。
Compile.prototype = {
//.....
compileElement: function(el) { //解析模版数据 判断是否是元素节点还是文本节点 有子节点的再次循环遍历
var childNodes = el.childNodes;
var self = this;
[].slice.call(childNodes).forEach(function(node) {
var reg = /\{\{(.*)\}\}/; //用来 标记一个子表达式的开始和结束位置
var text = node.textContent;
// 如果是元素节点
if(self.isElementNode(node)) {
self.compile(node);
// 如果是文本节点
} else if(self.isTextNode(node) && reg.test(text)) {
console.log(node);
self.compileText(node, reg.exec(text)[1]);
console.log(reg.exec(text)[0]);
}
// 如果元素有子节点,再回调compileElement函数
if(node.childNodes && node.childNodes.length) {
self.compileElement(node);
}
});
},
compile: function(node) { //解析元素指令 事件指令 绑定指令
var nodeAttrs = node.attributes; //元素属性
var self = this;
Array.prototype.forEach.call(nodeAttrs, function(attr) {
var attrName = attr.name;
if(self.isDirective(attrName)) {
var exp = attr.value;
var dir = attrName.substring(2);
if(self.isEventDirective(dir)) { //事件指令
self.compileEvent(node, self.vm, exp, dir);
} else { //v-model指令
self.compileModel(node, self.vm, exp, dir);
}
node.removeAttribute(attrName)
}
})
},
compileText: function(node, exp) { // 解析文本
var self = this;
var initText = this.vm[exp];
this.updateText(node, initText);
new Watcher(this.vm, exp, function(value) { //给文本添加一个监听器
self.updateText(node, value);
})
},
compileEvent: function(node, vm, exp, dir) { //解析事件
var eventType = dir.split(':')[1];
var cb = vm.methods && vm.methods[exp];
if(eventType && cb) { //增加一个监听事件
node.addEventListener(eventType, cb.bind(vm), false);
}
},
compileModel: function(node, vm, exp) { //解析model
var self = this;
var val = this.vm[exp];
this.modelUpdater(node, val);
new Watcher(this.vm, exp, function(value) { //给model添加一个监听器
self.modelUpdater(node, value);
})
node.addEventListener('input', function(e) {
var newValue = e.target.value;
if(val === newValue) {
return;
}
self.vm[exp] = newValue;
val = newValue;
});
},
updateText: function(node, value) { //
node.textContent = typeof value == 'undefined' ? '' : value;
},
modelUpdater: function(node, value) {
node.value = typeof value == 'undefined' ? '' : value;
},
isDirective: function(attr) {
return attr.indexOf('v-') == 0;
},
isEventDirective: function(dir) {
return dir.indexOf('on:') == 0;
},
isElementNode: function (node) {
return node.nodeType == 1;
},
isTextNode: function (node) {
return node.nodeType === 3;
}
}
通过递归遍历保证了每个节点和子节点会编译解析到,包括{{}}表达式里的文本节点。指令的声明规定,是通过特定前缀的节点属性来标记。监听数据和绑定更新函数是在compileUtil.bind()方法中,通过new Watcher()添加回调来接收数据变化的通知。
接下来实现一个watcher
三.实现一个Watcher
Watcher订阅者作为Observer和Compile之间通信的桥梁,主要做的是:
1. 在自身实例化时往属性订阅器(dep)中添加自己
2. 自身必须有一个update()方法
3. 待属性变动dep.notice()通知时,能调用自身的uodate()方法,并触发Compile中绑定的回调
实例化Watcher时,调用get()方法,通过Dep.target = watcherInstance标记订阅者是当前watcher实例,强行触发属性定义的getter方法,getter方法执行的时候,就会在属性订阅的dep添加当前的watcher实例,从而属性值变化的时候watcherInstance能接收到更新的通知。
四. 实现一个MVVM
function SelfVue(options) {
var self = this;
this.data = options.data;
this.methods = options.methods;
Object.keys(this.data).forEach(function(key) { //定义数据监听时object可以操作,不可枚举
self.proxyKeys(key);
})
observe(this.data); //监听数据
new Compile(options.el, this); //解析模板
options.mounted.call(this); //所有事情处理好后执行mounted
console.log(this.__proto__);
}
SelfVue.prototype = {
proxyKeys: function(key) {
var self = this;
Object.defineProperty(this, key, {
enumerable: false,
configurable: true,
get: function getter() {
return self.data[key]; // 数据读取
},
set: function setter (newVal) {
self.data[key] = newVal;
}
})
}
}
MVVM是负责安排observer,watcher,compile的工作
1. observer实现对MVVM自身model数据劫持,监听数据的属性变更,并在变动时进行notify
2. compile实现指令解析,初始化视图,并订阅数据变化,绑定好更新函数
3. watcher一方面接收observer通过Dep传递过来的数据变化,一方面通知compile进行视图的更新
到此我们的双向数据绑定就结束了。。。
来源:https://www.cnblogs.com/teteWang-Ember/p/10174980.html