zoukankan      html  css  js  c++  java
  • 手写MVVM

    // 创建一个Mvvm构造函数
    // 这里用es6方法将options赋一个初始值,防止没传,等同于options || {}
    function Mvvm(options = {}) {
    	// vm.$options Vue上是将所有属性挂载到上面
    	// 所以我们也同样实现,将所有属性挂载到了$options
    	console.log(options)
    	this.$options = options;
    	// this._data 这里也和Vue一样
    	let data = this._data = this.$options.data;
    
    	// 数据劫持
    	observe(data);
    	// this 代理了this._data
    	for(let key in data) {
    		Object.defineProperty(this, key, {
    			configurable: true,
    			get() {
    				return this._data[key]; // 如this.a = {b: 1}
    			},
    			set(newVal) {
    				this._data[key] = newVal;
    			}
    		});
    	}
    	// 初始化computed,将this指向实例
    	initComputed.call(this);
    	// 编译    
    	new Compile(options.el, this);
    	// 所有事情处理好后执行mounted钩子函数
    	options.mounted.call(this); // 这就实现了mounted钩子函数
    }
    // 创建一个Observe构造函数
    // 写数据劫持的主要逻辑
    function Observe(data) {
    	let dep = new Dep();
    	// 所谓数据劫持就是给对象增加get,set
    	// 先遍历一遍对象再说
    	for(let key in data) { // 把data属性通过defineProperty的方式定义属性
    		let val = data[key];
    		observe(val); // 递归继续向下找,实现深度的数据劫持
    		Object.defineProperty(data, key, {
    			configurable: true,
    			get() {
    				Dep.target && dep.addSub(Dep.target);
    				return val;
    			},
    			set(newVal) { // 更改值的时候
    				if(val === newVal) { // 设置的值和以前值一样就不理它
    					return;
    				}
    				val = newVal; // 如果以后再获取值(get)的时候,将刚才设置的值再返回去
    				observe(newVal); // 当设置为新值后,也需要把新值再去定义成属性
    				dep.notify(); // 让所有watcher的update方法执行即可
    			}
    		});
    	}
    }
    
    // 外面再写一个函数
    // 不用每次调用都写个new
    // 也方便递归调用
    function observe(data) {
    	// 如果不是对象的话就直接return掉
    	// 防止递归溢出
    	if(!data || typeof data !== 'object') return;
    	return new Observe(data);
    }
    
    // 创建Compile构造函数
    function Compile(el, vm) {
    	// 将el挂载到实例上方便调用
    	vm.$el = document.querySelector(el);
    	console.log(vm.$el)
    	// 在el范围里将内容都拿到,当然不能一个一个的拿
    	// 可以选择移到内存中去然后放入文档碎片中,节省开销
    	let fragment = document.createDocumentFragment();
    	console.log(fragment)
    	console.log(vm.$el.firstChild)
    	while(child = vm.$el.firstChild) {
    		fragment.appendChild(child); // 此时将el中的内容放入内存中
    	}
    	console.log(fragment.childNodes)
    	// 对el里面的内容进行替换
    	function replace(frag) {
    		Array.from(frag.childNodes).forEach(node => {
    			console.log(node, node.textContent)
    			let txt = node.textContent;
    			let reg = /{{(.*?)}}/g; // 正则匹配{{}}
    
    			if(node.nodeType === 3 && reg.test(txt)) { // 即是文本节点又有大括号的情况{{}}
    				//				console.log(RegExp.$1); // 匹配到的第一个分组 如: a.b, c
    				//				let arr = RegExp.$1.split('.');
    				//				console.log(arr)
    				//				let val = vm;
    				//				arr.forEach(key => {
    				//					val = val[key]; // 如this.a.b
    				//				});
    				//				// 用trim方法去除一下首尾空格
    				//				node.textContent = txt.replace(reg, val).trim();
    				function replaceTxt() {
    					node.textContent = txt.replace(reg, (matched, placeholder) => {
    						console.log(placeholder); // 匹配到的分组 如:song, album.name, singer...
    						new Watcher(vm, placeholder, replaceTxt); // 监听变化,进行匹配替换内容
    
    						return placeholder.split('.').reduce((val, key) => {
    							return val[key];
    						}, vm);
    					});
    				};
    				// 替换
    				replaceTxt();
    
    			}
    			if(node.nodeType === 1) { // 元素节点
    				let nodeAttr = node.attributes; // 获取dom上的所有属性,是个类数组
    				console.log(node.attributes)
    				Array.from(nodeAttr).forEach(attr => {
    					let name = attr.name; // v-model  type
    					let exp = attr.value; // c        text
    					if(name.includes('v-')) {
    						node.value = vm[exp]; // this.c 为 2
    					}
    					console.log(attr.value)
    					//					debugger
    					// 监听变化
    					new Watcher(vm, exp, function(newVal) {
    						node.value = newVal; // 当watcher触发时会自动将内容放进输入框中
    					});
    
    					node.addEventListener('input', e => {
    						debugger
    						let newVal = e.target.value;
    						// 相当于给this.c赋了一个新值
    						// 而值的改变会调用set,set中又会调用notify,notify中调用watcher的update方法实现了更新
    						vm[exp] = newVal;
    						console.log(vm)
    					});
    				});
    			}
    
    			// 如果还有子节点,继续递归replace
    			if(node.childNodes && node.childNodes.length) {
    				replace(node);
    			}
    		});
    		// 监听变化
    		// 给Watcher再添加两个参数,用来取新的值(newVal)给回调函数传参
    		new Watcher(vm, RegExp.$1, newVal => {
    			node.textContent = txt.replace(reg, newVal).trim();
    		});
    
    	}
    
    	replace(fragment); // 替换内容
    
    	vm.$el.appendChild(fragment); // 再将文档碎片放入el中
    }
    
    // 发布订阅模式  订阅和发布 如[fn1, fn2, fn3]
    function Dep() {
    	// 一个数组(存放函数的事件池)
    	this.subs = [];
    }
    Dep.prototype = {
    	addSub(sub) {
    		this.subs.push(sub);
    	},
    	notify() {
    		// 绑定的方法,都有一个update方法
    		this.subs.forEach(sub => sub.update());
    	}
    };
    // 监听函数
    // 通过Watcher这个类创建的实例,都拥有update方法
    function Watcher(vm, exp, fn) {
    	this.fn = fn; // 将fn放到实例上
    	this.vm = vm;
    	this.exp = exp;
    	// 添加一个事件
    	// 这里我们先定义一个属性
    	Dep.target = this;
    	console.log(exp)
    	let arr = toString(exp).split('.');
    	let val = vm;
    	arr.forEach(key => { // 取值
    		val = val[key]; // 获取到this.a.b,默认就会调用get方法
    	});
    	Dep.target = null;
    }
    Watcher.prototype.update = function() {
    	// notify的时候值已经更改了
    	// 再通过vm, exp来获取新的值
    	console.log(this.exp)
    	let arr = toString(this.exp).split('.');
    	let val = this.vm;
    	arr.forEach(key => {
    		val = val[key]; // 通过get获取到新的值
    	});
    	console.log(this)
    	this.fn(val);
    };
    
    let watcher = new Watcher(() => console.log(111)); // 
    let dep = new Dep();
    dep.addSub(watcher); // 将watcher放到数组中,watcher自带update方法, => [watcher]
    dep.addSub(watcher);
    dep.notify(); //  111, 111
    function initComputed() {
    	let vm = this;
    	let computed = this.$options.computed; // 从options上拿到computed属性   {sum: ƒ, noop: ƒ}
    	// 得到的都是对象的key可以通过Object.keys转化为数组
    	Object.keys(computed).forEach(key => { // key就是sum,noop
    		Object.defineProperty(vm, key, {
    			// 这里判断是computed里的key是对象还是函数
    			// 如果是函数直接就会调get方法
    			// 如果是对象的话,手动调一下get方法即可
    			// 如: sum() {return this.a + this.b;},他们获取a和b的值就会调用get方法
    			// 所以不需要new Watcher去监听变化了
    			get: typeof computed[key] === 'function' ? computed[key] : computed[key].get,
    			set() {}
    		});
    	});
    }
    

      

    二:

    index.html

    <!DOCTYPE html>
    <html lang="en">
    <head>
    	<meta charset="UTF-8">
    	<title>双向绑定实现</title>
    	<style>
    		#app {
    			text-align: center;
    		}
    	</style>
    </head>
    <body>
    	<div id="app">
    		<h2>{{title}}</h2>	
    		<input v-model="name">
    		<h1>{{name}}</h1>
    		<button v-on:click="clickMe">点击!</button>
    	</div>
    </body>
    <script src="js/observer.js"></script>
    <script src="js/watcher.js"></script>
    <script src="js/compile.js"></script>
    <script src="js/index.js"></script>
    <script>
    
    	new Vue({
    		el: '#app',
    		data: {
    			title: 'vue code',
    			name: '练习'
    		},
    		methods: {
    			clickMe() {
    				this.title = 'vue code click';
    			}
    		},
    		mounted() {
    			window.setTimeout(() => {
    				this.title = '1秒';
    			}, 1000);
    		}
    	});
    
    </script>
    </html>
    

    index.js

    function Vue(options) {
    	var self = this;
    	this.data = options.data;
    	this.methods = options.methods;
    
    	Object.keys(this.data).forEach(function(key) {
    		console.log(key);	// title  name
    		self.proxyKeys(key);
    	});
    
    	observe(this.data);
    	new Compile(options.el, this);
    	options.mounted.call(this);		// 所有事情处理好后执行mounted函数
    }
    
    Vue.prototype = {
    	proxyKeys: function(key) {
    		var self = this;
    		Object.defineProperty(this, key, {
    			enumerable: false,
    			configurable: true,
    			get: function() {
    				return self.data[key];
    			},
    			set: function(newVal) {
    				self.data[key] = newVal;
    			}
    		});
    	}
    };
    

      

     

     observer.js

    function Observer(data) {
    	this.data = data;
    	this.walk(data);
    }
    
    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();
    		
    		Object.defineProperty(data, key, {
    			enumerable: true,
    			configurable: true,
    			get: function getter() {
    				if (Dep.target) {
    					console.log(Dep.target)
    					dep.addSub(Dep.target);
    				}
    				return val;
    			},
    			set: function setter(newVal) {
    				if (newVal === val) {
    					return;
    				}
    				val = newVal;
    				dep.notify();
    			}
    		});
    	}
    };
    
    function observe(val, vm) {
    	if (!val || typeof val !== 'object') {
    		return;
    	}
    	return new Observer(val);
    }
    
    function Dep() {
    	this.subs = [];
    }
    Dep.prototype = {
    	addSub: function(sub) {
    		this.subs.push(sub);
    	},
    	notify: function() {
    		this.subs.forEach(function(sub) {
    			console.log(sub.update)
    			sub.update();
    		});
    	}
    };
    Dep.target = null;
    

      watcher.js

    function Watcher(vm, exp, cb) {
    	this.vm = vm;
    	this.exp = exp;
    	this.cb = cb;
    	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;
    	}
    }
    

      compile.js

    function Compile(el, vm) {
    	this.vm = vm;
    	this.el = document.querySelector(el);
    	this.fragment = null;
    	this.init();
    }
    
    Compile.prototype = {
    	init: function() {
    		if (this.el) {
    			this.fragment = this.nodeToFragment(this.el);
    			this.compileElement(this.fragment);
    			this.el.appendChild(this.fragment);
    		} else {
    			console.log('Dom元素不存在');
    		}
    	},	
    	nodeToFragment: function(el) {
    		var fragment = document.createDocumentFragment();
    		var child = el.firstElementChild;
    
    		while (child) {
    			// 将Dom元素移入fragment中
    			fragment.appendChild(child);
    			child = el.firstElementChild;
    		}
    
    		return fragment;
    	},
    	compileElement: function(el) {
    		var self = this;
    		var childNodes = el.childNodes;
    
    		[].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)) {
    				self.compileText(node, reg.exec(text)[1]);
    			}
    
    			if (node.childNodes && node.childNodes.length) {
    				self.compileElement(node);
    			}
    		});
    	},
    	compile: function(node) {
    		var self = this;
    		var nodeAttrs = node.attributes;
    
    		Array.prototype.forEach.call(nodeAttrs, function(attr) {
    			var attrName = attr.name;
    			if (self.isDirective(attrName)) {
    				var exp = attr.value;
    				var dir = attrName.substring(2);	// model  on:click
    				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, dir) {
    		var self = this;
    		var val = this.vm[exp];		// name
    		this.modelUpdater(node, val);
    
    		new Watcher(this.vm, exp, function(value) {
    			self.modelUpdater(node, value);
    		});
    
    		node.addEventListener('input', function(e) {
    			var newVal = e.target.value;
    
    			if (val === newVal) {
    				return;
    			}
    
    			self.vm[exp] = newVal;
    			val = newVal;
    		});
    	},
    	updateText: function(node, value) {
    		node.textContent = typeof value === 'undefined' ? '' : value;
    	},
    	modelUpdater: function(node, value, oldVal) {
    		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;
    	}
    };
    

      

  • 相关阅读:
    HDU 1051
    HDU 1236
    递归求gcd(a,b)
    HDU 1372
    HDU 1312
    HDU 1253
    HDU 1072
    ...别人的推荐、
    搜索总结、
    TortoiseHg简单的入门使用说明
  • 原文地址:https://www.cnblogs.com/ygunoil/p/15173829.html
Copyright © 2011-2022 走看看