分享:你会写一个vue的双向数据绑定吗?

422 阅读1分钟

<!DOCTYPE html>
<html>
<head>
	<title></title>
</head>
<body>
	<div id="app">
		<input type="text" name="" v-model='num'>
		<button v-click='addNum'>点击</button>
		<h1 v-bind='num'></h1>
	</div>
</body>
</html>
<script type="text/javascript">
	function MyVue(options) {
		this._init(options);
	}
	MyVue.prototype._init = function(options) {
		this.$options = options;
		this.$data = options.data;
		this.$el = document.querySelector(options.el);
		this.$methods = options.methods;

		this._binding = {}; 
		/**
		* _binding保存着model与view的映射关系,也就是我们前面定义的Watcher的实例。
		* 当model改变时,我们会触发其中的指令类更新,保证view也能实时更新
		*/
		this._obverse(this.$data);
		this._compile(this.$el);
	};

	MyVue.prototype._obverse = function (obj) {
		var value;
		for(var i in obj){
			if(obj.hasOwnProperty(i)){
				value = obj[i];
				this._binding[i] = {
					_directives:[]
				}

				var binding = this._binding[i];

				if(typeof value === 'object'){
					this._obverse(value);
				}

				Object.defineProperty(this.$data,i,{
					enumerable:true,
					configurable:true,
					get:function () {
						console.log("获取值"+value);
						return value;
					},
					set:function (newVal) {
						console.log("设置值"+value);
						if(value !== newVal){
							value = newVal;
							binding._directives.forEach(function (item) {
								item.update();
							})
						}
					}
				})
			}
		}
	}

	MyVue.prototype._compile = function (root) {
		var _this = this;
		var nodes = root.children;
		for(var n = 0;n<nodes.length;n++){
			(function (node) {
				if(node.children.length !== 0){
					_this._compile(node);
				}

				if(node.hasAttribute('v-click')){
					var attrVal = node.getAttribute('v-click');
					node.addEventListener('click',(function () {
						return _this.$methods[attrVal].bind(_this.$data);
					})())
				}

				if(node.hasAttribute('v-bind')){
					var attrVal = node.getAttribute('v-bind');
					_this._binding[attrVal]._directives.push(new Watcher('text',node,_this,attrVal,'innerHTML'));
				}

				if(node.hasAttribute('v-model') && (node.tagName === 'INPUT' || node.tagName === 'TEXTAREA')){
					var attrVal = node.getAttribute('v-model');
					node.addEventListener('input',(function (key) {
						_this._binding[attrVal]._directives.push(new Watcher('input',node,_this,attrVal,'value'));
						return function () {
							_this.$data[attrVal] = node.value;
						}
					})(n));
				}
			})(nodes[n])
		}
	}

	function Watcher(name,el,vm,exp,attr) {
		this.name = name; //指令的名称
		this.el = el; //指令对应的节点
		this.vm = vm; //指令所属的vm实例
		this.exp = exp; // 指令对应的值
		this.attr = attr; //指令所需要的改变的属性

		this.update();
	}

	Watcher.prototype.update = function () {
		this.el[this.attr] = this.vm.$data[this.exp];
	}

	var app = new MyVue({
		el:'#app',
		data:{
			num:0
		},
		methods:{
			addNum:function () {
				console.log('num');
				this.num++;
			}
		}
	});
</script>