【手写】MVVM原理3

此博客主要涉及Vue数据双向绑定原理,虽目前仅提及compile.js且后续待补充解说,但核心围绕Vue的MVVM模式及数据双向绑定,运用订阅发布机制实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

刚写的原理,先放上来再说,后续补充解说

compile.js

/*
	简单手写一把数据的编译,主要支持:
		文本编译
		输入框model编译
	功能可以扩充,重在编译的思想。
*/
class Compile{
	constructor(el,vm){
		this.el = this.isElementNode(el) ? el : document.querySelector(el);//#app document.query。。。得区分开来是哪个
		this.vm = vm;
		if(this.el){//如果这个元素能获取到,我们才开始编译
			/*
				1. 编译就要先获取dom,如何获取?
					方案一(劣):一个个DOM获取,然后编译一个,放到页面上去,这样频繁操作dom性能不好,不建议
					方案二(优):先把这些真实的Dom移入到内存中  利用文档碎片fragment
				2. 编译=>提取想要的元素节点  v-model 和 文本节点{{}}
				3. 把编译好的fragment再塞回到页面里去
			*/

			//1. 将真实dom放到内存中
			let fragment = this.node2fragment(this.el);
			//2. 编译文档碎片,提取想要的元素节点  v-model 和 文本节点{{}}
			this.compile(fragment);
			//3. 编译完,塞回去
			this.el.appendChild(fragment);
		}
	}
	/*专门写一些辅助方法*/
	//判断el是否为dom
	isElementNode(node){
		/*判断是否为元素节点*/
		return node.nodeType === 1;
	}
	//判断是否为指令
	isDirective(name){
		return name.includes("v-");
	}



	/*专门写一些核心方法*/
	//编译元素
	compileElement(node){//假设编译带v-model的
		//获取节点的属性
		let attrs = node.attributes;//返回的attrs是一个map映射,类数组,可循环
		Array.from(attrs).forEach(attr => {
			//console.log(attr);//得到的是type="text"等属性和属性值的对,是对象类型,key为"name"
			//判断属性名字是否包含v-
			let attrName = attr.name;
			if(this.isDirective(attrName)){
				//取到对应的值,放到节点的中
				let expr = attr.value;
				let type = attrName.slice(2);
				//找到节点node,去数据this.vm.$data中把对应的值取出来,放到对应的节点中
				CompileUtil[type](node,this.vm,expr);
			}
		})

	}

	//编译文本
	compileText(node){
		//带{{}},但是不能是{{asas}sas}}
		let expr = node.textContent;//取出文本中的内容
		let reg = /\{\{([^}]+)\}\}/g;//要考虑到多个{{}}组成的一个文本
		if(reg.test(expr)){
			//node this.vm.$data
			CompileUtil['text'](node,this.vm,expr);
		}else{

		}
	}


	//编译碎片
	compile(fragment){
		//拿到所有的节点,包括子节点
		//需要递归
		let childNodes = fragment.childNodes;
		Array.from(childNodes).forEach(node => {
			if(this.isElementNode(node)){
				//如果是元素节点,还需要继续深入的检查子节点是元素还是文本
				//这里需要编译元素
				this.compileElement(node);
				this.compile(node);
				
			}else {
				//文本节点,看看是否有大括号{}
				//这里需要编译文本
				this.compileText(node);
			}
		})
	}


	//把真实dom放入内存中
	node2fragment(el){
		//先创建一个内存中的文档碎片(不是真实的),用于存放拷贝过来的真实DOM,变成内存中的DOM节点
		let fragment = document.createDocumentFragment();

		let firstChild;
		while(firstChild = el.firstChild){
			fragment.appendChild(firstChild);
		}
		return fragment;
	}

}

//专门用于编译的对象,假如要编译html,那只要在这个对象里加方法就可以了
CompileUtil = {
	text(node,vm,expr){//文本处理,expr可能是{{s}} {{sc}} {{sds}}
		let updateFn = this.update['textUpdater'];
		let value = this.getTextVal(expr,vm);

		expr.replace(/\{\{([^}]+)\}\}/g,(...arguments) => {
			new Watcher(vm,arguments[1],(newValue) => {
				//如果数据变化了,文本节点需要重新获取依赖的数据,更新文本中的内容
				updateFn && updateFn(node,this.getTextVal(expr,vm));
			})
		})
		

		updateFn && updateFn(node,value);
	},
	model(node,vm,expr){//输入框的处理
		let updateFn = this.update['modelUpdater'];
		//let data = vm.$data[expr];//这样不行,因为expr可能是"message.a",也就是说可能是获取data中message下的属性a
		//所以,针对以上情况,我们需要根据“.”来split,然后获取到对应的属性值

		//这里应该加一个监控,数据变化了,就应该调用下watch的callback
		new Watcher(vm,expr,(newVal) => {
			//当值变化后,会调用cb,将新值传递过来
			updateFn && updateFn(node,this.getVal(expr,vm));
		});
		node.addEventListener('input',(e) => {
			let newValue = e.target.value;
			this.setVal(expr,vm,newValue);
		})
		updateFn && updateFn(node,this.getVal(expr,vm));
		
	},
	update:{
		//文本更新
		textUpdater(node,value){
			node.textContent = value;
		},
		//输入框更新
		modelUpdater(node,value){
			node.value = value;
		}
	},
	getVal(expr,vm){//获取实例上对应的数据
		//reduce
		expr = expr.split('.');//[message,a,b,c]
		return expr.reduce((prev,next) => {//vm.$data.message
			return prev[next];
		},vm.$data)
	},
	getTextVal(expr,vm){
		return expr.replace(/\{\{([^}]+)\}\}/g,(...arguments) => {
			return this.getVal(arguments[1],vm);
		})
	},
	setVal(expr,vm,value){//expr可能为[message,a]
		expr= expr.split('.');
		expr.reduce((prev,next,currentIndex) => {
			if(currentIndex === expr.length - 1){
				return prev[next] = value;
			}
			return prev[next];
		},vm.$data);
	}
}













评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值