Vue原理解析:手寫編譯器(節點解析) —— Compile

LaoYe - IT發表於2021-01-04

由於時間問題,暫時先把程式碼完整的貼上來,感興趣的朋友可以自行研究或收藏。等我那時有時間的時候,對專欄文章進行排序,並逐一講解程式碼

一、宣告式HTML

<div id="app">
	<h3>Hello,{{personName}},你在{{msg}}嗎?</h3>
	<div v-text="msg" v-on:click="handleShowTip"></div>
	<div v-text="msg" @click="handleShowMsg(msg, personName)"></div>
	<div v-html="htmlStr"></div>
</div>

<script src="src/MVue.js" type="text/javascript" charset="utf-8"></script>
<script type="text/javascript">
	let vm = new MVue({
		el: "#app",
		data: {
			msg: "學習MVVM原理",
			htmlStr: "<h4>你學的怎麼樣?</h4>",
			personName: "張三",
			test: {
				name: "張三"
			}
		},
		methods: {
			handleShowMsg(key) {
				this.msg = "hhx";
			},
			handleShowTip() {
				console.log(this);
			}
		}
	})
</script>

二、編譯器處理例項

class Compile {
	constructor(el, vm) {
		this.vm = vm;
		this.el = this.isElementNode(el) ? el : document.querySelector(el);
		// 1.獲取文件碎片物件,放入記憶體中會減少頁面的迴流和重繪
		const fragment = this.node2Fragment(this.el);
		// 2.編譯模板
		this.compile(fragment);
		// 3.追加子元素到根元素
		this.el.appendChild(fragment)
	}

	compile(fragment) {
		// 1.獲取子節點
		const childNodes = fragment.childNodes;
		childNodes.forEach(child => {
			if (this.isElementNode(child)) {
				// 編譯元素節點
				this.compileElement(child);
			} else {
				// 編譯文字節點
				this.compileText(child);
			}

			if (child.childNodes && child.childNodes.length) {
				this.compile(child);
			}
		})
	}

	compileText(node) {
		const content = node.textContent;
		if (/\{\{(.+?)\}\}/.test(content)) {
			compileUntil["text"](node, content, this.vm);
		}
	}

	compileElement(node) {
		const attrs = node.attributes;
		[...attrs].forEach((attr) => {
			const {
				name,
				value
			} = attr;

			console.log(attr);

			if (this.isDirective(name)) {
				const [, diractive] = name.split("-");
				const [dirName, eventName] = diractive.split(":");

				// 更新資料,資料驅動檢視
				compileUntil[dirName](node, value, this.vm, eventName);

				// 刪除標籤上的指令
				node.removeAttribute("v-" + diractive);
			}

			if (this.isEvent(name)) {
				const [, eventName] = name.split("@");
				compileUntil["on"](node, value, this.vm, eventName);
			}
		});
	}

	isEvent(attrName) {
		return attrName.startsWith("@");
	}

	isDirective(attrName) {
		return attrName.startsWith("v-");
	}

	node2Fragment(el) {
		// 建立文件碎片
		const f = document.createDocumentFragment();
		let firstChild;
		while (firstChild = el.firstChild) {
			f.appendChild(firstChild);
		}
		return f;
	}

	isElementNode(node) {
		return node.nodeType === 1
	}
}

三、編譯器解析工具

const compileUntil = {
	getValue(expr, vm) {
		// 切分物件屬性,不斷的累加到目的屬性
		return expr.split(".").reduce((data, currentVal) => {
			return data[currentVal];
		}, vm.$data);
	},
	text(node, expr, vm) {
		let value;
		// 判斷是否存在變數引數, 如果存在則將變數物件值替換掉宣告變數
		if (expr.indexOf("{{") !== -1) {
			value = expr.replace(/\{\{(.+?)\}\}/g, (...args) => {
				return this.getValue(args[1], vm);
			});
		} else {
			value = this.getValue(expr, vm);
		}
		// 更新檢視
		this.updater.textUpdater(node, value);
	},
	html(node, expr, vm) {
		const value = this.getValue(expr, vm);
		this.updater.htmlUpdater(node, value);
	},
	model(node, expr, vm) {
		const value = this.getValue(expr, vm);
		this.updater.modelUpdater(node, value);
	},
	bind(node, expr, vm, attrName) {

	},
	on(node, expr, vm, eventName) {
		let param = [];
		let fnName = expr.replace(/\((.+?)\)/g, (...args) => {
			param = args[1].split(",");
			return "";
		});
		let fn = vm.$options.methods && vm.$options.methods[fnName];
		node.addEventListener(eventName, fn.bind(vm, ...param), false);
	},
	updater: {
		textUpdater(node, value) {
			node.textContent = value;
		},
		htmlUpdater(node, value) {
			node.innerHTML = value;
		},
		modelUpdater(node, value) {
			node.value = value;
		}
	}
}

 

相關文章