Vue.js双向绑定的实现原理

2016/12/10 00:08
阅读数 347
<!DOCTYPE html>
<html>
<head>
	<title>Vue.js双向绑定的实现原理</title>
</head>
<body>
<div id="app">
	<input type="text" v-model="text">
	{{ text }}
</div>

<script type="text/javascript">


//订阅者
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) {
	Dep.target = this;	
	this.name = name;
	this.node = node;
	this.vm = vm;
	this.update();
	Dep.target = null;
}

Watcher.prototype = {
	update: function () {
		this.get();
		this.node.nodeValue = this.value;
	},
	// 获取data中的属性值
	get: function () {
		this.value = this.vm[this.name]; // 触发相应属性的get
	}
}


//对象访问器
function defineReactive(obj,key,val) {
	var dep = new Dep();
	Object.defineProperty(obj,key,{
		get:function(){
			// 添加订阅者watcher到主题对象Dep
			if(Dep.target)dep.addSub(Dep.target);
			console.log(dep.subs)
			return val;
		},
		set:function(newVal){
			if(newVal === val) return;
			val=newVal;
			//作为发布者发布通知
			dep.notify();
			console.log(val);
		}
	})
}

//观察者,观察data对象的变更,使用Object.defineProperty添加订阅者和发布通知
function observe(obj,vm){
	Object.keys(obj).forEach(function(key){
		defineReactive(vm,key,obj[key]);
	});
}

//编译方法
function compile(node,vm){
	var reg = /\{\{(.*)\}\}/;

	//节点类型为元素
	if(node.nodeType === 1){
		var attr = node.attributes;
		for(var i=0;i<attr.length;i++){
			if(attr[i].nodeName == 'v-model'){
				var name = attr[i].nodeValue;
				node.addEventListener('input',function(e){
					vm[name] = e.target.value;
				});
				node.value = vm[name];
				node.removeAttribute('v-model');
			}
		}
	}

	//节点类型为text
	if(node.nodeType ===3){
		if(reg.test(node.nodeValue)){
			var name = RegExp.$1;
			name = name.trim();
			new Watcher(vm,node,name);
		}
	}
}

//使用DocumentFragment劫持挂载的目标节点
function nodeToFragment(node,vm){
	var flag = document.createDocumentFragment();
	var child;
	while(child = node.firstChild){
		compile(child,vm);
		flag.append(child);
	}
	return flag;
}

function Vue(options){
	this.data = options.data;
	var data = this.data;

	observe(data,this);

	var id = options.el;
	var dom = nodeToFragment(document.getElementById(id),this);
	//编译完成后将dom返回到app中
	document.getElementById(id).appendChild(dom);
}

var vm = new Vue({
	el:'app',
	data:{
		text:'hello world'
	}
})
</script>
</body>
</html>

 

展开阅读全文
加载中
点击引领话题📣 发布并加入讨论🔥
0 评论
0 收藏
0
分享
返回顶部
顶部