1,772   JS Vuejs

 

MVVM 模式使得视图 view 和 model 交互变得极为方便, view 的改变立即同步到 model ,model 的改变也可立即同步到 view;

vuejs 实现 MVVM 模式主要通过 Object.defineProperty() 来侦听属性的 set 和 get 的动作,然后结合发布者-订阅者模式传达出去,达到数据立即同步的效果

流程如下
132184689-57b310ea1804f_articlex

  • 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'
			}
		});

完整代码,请查看这里




Trackbacks/Pingbacks

  1.  ES5的主要新特性 | LuckyBird

Leave a Reply

Your email address will not be published. Required fields are marked *