vue-双向数据绑定原理分析二--Mr.Emberhtml
摘要:双向数据绑定是vue的核心,经过是数据劫持结合发布者-订阅者模式的模式实现的。vue
上一讲提到了怎么简单的模仿双向数据绑定,下面咱们将会分析下vue双向数据绑定源码的简单实现。node
实现原理数组
双向数据绑定是经过数据的劫持结合发布者-订阅者模式的方式实现的。浏览器
数据劫持、vue是经过Object.definedProperty()实现数据劫持,其中会有getter()和setter()方法。当读取属性值时,就会触发getter()方法,在view中若是数据发生了变化,就会经过Object.defineProperty()对属性设置一个setter函数,当数据改变了就会触发这个函数;app
一. 实现一个监听器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;
二. 实现一个Compilethis
compile主要作的是解析模板指令,将模板的变量替换成数据,而后初始化渲染页面视图,并将每一个指令对应的节点绑定更新函数,添加监听数据的订阅者,一旦数据有变更,收到通知,更新视图。spa
由于遍历解析的过程有屡次操做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进行视图的更新
到此咱们的双向数据绑定就结束了。。。