MVVM 模式使得视图 view 和 model 交互变得极为方便, view 的改变立即同步到 model ,model 的改变也可立即同步到 view;
vuejs 实现 MVVM 模式主要通过 Object.defineProperty() 来侦听属性的 set 和 get 的动作,然后结合发布者-订阅者模式传达出去,达到数据立即同步的效果
- Observer 数据监听器,能够对数据对象的所有属性进行监听,如有变动可拿到最新值并通知订阅者,内部采用 Object.defineProperty 的 get 和 set 来实现。
- Compile 指令解析器,它的作用对每个元素节点的指令进行扫描和解析,根据指令模板替换数据,以及绑定相应的更新函数。
- Watcher 订阅者, 作为连接 Observer 和 Compile 的桥梁,能够订阅并收到每个属性变动的通知,执行指令绑定的相应回调函数。
- Dep 消息订阅器,内部维护了一个数组,用来收集订阅者 Watcher,数据变动触发 notify 函数,再调用订阅者的 update 方法。
下面用简单的代码实现以上功能,请留意看注释
1, observer
// 观察者 function observer(data,vm) { if (!data || typeof data !== 'object') { return; } // 取出所有属性遍历,绑定 set 和 get Object.keys(data).forEach(function(key) { defineReactive(vm, key, data[key]); }); }; function defineReactive(vm, key, val) { var dep = new Dep(); Object.defineProperty(vm, key, { enumerable: true, // 可枚举 configurable: false, // 不能再define get: function() { // 获取 val 时被触发 console.log('get'); // 增加订阅者到容器 if (Dep.target) dep.addSub(Dep.target); return val; }, set: function(newVal) { // 设置 val 时被触发 console.log('get'); if(val== newVal) return; val = newVal; // 通知订阅者更新 dep.notify(); } }); }
2, watcher
// 订阅者容器 function Dep() { this.subs = []; } Dep.prototype = { // 增加订阅者 addSub: function(sub) { this.subs.push(sub); }, // 通知订阅者更新 notify: function() { // 遍历所有订阅者进行更新 this.subs.forEach(function(sub) { sub.update(); }); } }; // 订阅者 function Watcher (vm, node, name, nodeType) { Dep.target = this; this.name = name; this.node = node; this.vm = vm; this.nodeType = nodeType; // 首次运行会触发 console.log('init and update'); this.update(); Dep.target = null; } Watcher.prototype = { // data 变化触发 set,通知订阅者更新 update: function () { this.get(); // 更新 text if (this.nodeType == 'text') { this.node.nodeValue = this.value; } // 更新 input if (this.nodeType == 'input') { this.node.value = this.value; } }, // 获取 data 中的属性值 get: function () { console.log('this.nodeType:'+this.nodeType) this.value = this.vm[this.name]; // 触发相应属性的 get } }
3,compile
// 解析 dom 文档 function nodeToFragment (node, vm) { var flag = document.createDocumentFragment(); var child; while (child = node.firstChild) { // 翻译事件命令和变量 compile(child, vm); flag.append(child); // 将子节点劫持到文档片段中 } return flag; } // 翻译事件命令和变量 function compile (node, vm) { var reg = /\{\{(.*)\}\}/; // 节点类型为元素 if (node.nodeType === 1) { var attr = node.attributes; // 解析属性 for (var i = 0; i < attr.length; i++) { // 翻译 v-model 命令 if (attr[i].nodeName == 'v-model') { var name = attr[i].nodeValue; // 侦听 input 输入 node.addEventListener('input', function (e) { // 将 input 变化会同步到 data // view 同步到 model // 触发属性的 set 方法 console.log('listen,input,set') vm[name] = e.target.value; }); // 首次运行会触发 console.log('init and get'); // 将默认 data 的值赋给该 input node.value = vm[name]; node.removeAttribute('v-model'); } }; // 初始化订阅者 input // data 变化将会自动同步到 input value // model 同步到 view new Watcher(vm, node, name, 'input'); } // 节点类型为text if (node.nodeType === 3) { if (reg.test(node.nodeValue)) { var name = RegExp.$1; // 获取匹配到的字符串 name = name.trim(); // 初始化订阅者 text // data 变化将会自动同步到 text // model 同步到 view new Watcher(vm, node, name, 'text'); } } }
4, Vuejs MVVM
// Vue 对象,实现 MVVM function Vue (options) { this.data = options.data; var data = this.data; // 初始化观察者,添加订阅者 observer(data, this); var id = options.el; var app = document.querySelector(id); // 解析文档命令事件和变量 var dom = nodeToFragment(app, this); // 编译完成后,将 dom 返回到 app 中 app.appendChild(dom); } var vm = new Vue({ el: '#app', data: { message: 'hello world' } });
完整代码,请查看这里
Leave a Reply