手写模板引擎

319 阅读2分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

模板引擎

阅读vue和mustache源码后,自己实现一个简易版的模板引擎

实现思路

1、获取页面模板字符串

let template = document.getElementById(id).innerHTML

2、解析成树结构tokens数组,通过栈管理
在这里插入图片描述
3、 将数据替换上去然后渲染到页面上

// 渲染
function resuleStr(Tokens, data) {
	let resuleStr = ''
	Tokens.forEach((item, index) => {
		if (item[0] == 'text') {
			resuleStr += item[1]
		} else if (item[0] == 'name') {
			resuleStr += lookup(data, item[1])
		} else if (item[0] == '#') {
			resuleStr += arrRender(item[2], data[item[1]])
		}
	})
	return resuleStr
}
// 循环数组处理
function arrRender(template, arrData) {
	let str = ''
	arrData.forEach((item) => {
		str += resuleStr(template, item)
	})
	return str
}

代码

<!DO
CTYPE html>
<html>
	<head>
		<meta charset="utf-8">
		<title></title>
	</head>
	<script src="./templateEngine.js"></script>
	<body>
		<div id="container">
			<div>
				 <ul>
					{{#students}}
						<li class="myli">
							学生{{name}}的爱好是
							<ol>
								{{#hobbies}}
									<li>{{.}}</li>
								{{/hobbies}}
							</ol>
							<h1>{{a.b.c}}</h1>
						</li>
					{{/students}}
				</ul>
			</div>
		</div>
		<script>
			// 数据
			let data = {
				students: [{
						'name': '小明',
						'hobbies': ['编程', '游泳'],
						a: {
							b: {
								c: '多层级测试1'
							}
						}
					},
					{
						'name': '小红',
						'hobbies': ['看书', '弹琴', '画画'],
						a: {
							b: {
								c: '多层级测试2'
							}
						}
					},
					{
						'name': '小强',
						'hobbies': ['锻炼'],
						a: {
							b: {
								c: '多层级测试3'
							}
						}
					}
				]
			};
			render('container', data)
		</script>
	</body>
</html>

templateEngine文件

class scanner {
	constructor(template) {
		this.index = 0 //下标
		this.tail = template // 尾部
		this.template = template
	}
	sacnText(tag) {
		var startIndex = this.index; // 记录开始下标
		while (this.tail.indexOf(tag) !== 0 && this.tail) {
			this.index++
			this.tail = this.template.substr(this.index)
		}
		var endText = this.template.substring(startIndex, this.index)
		return endText
	}
	sacnTag(tag) {
		this.index += tag.length
		this.tail = this.tail.substring(tag.length)
		return this.tail.substr(0, tag.length)
	}
}
// 获取tokens
function getTokens(template) {
	var token = new scanner(template)
	var tokenArr = []
	var word
	while (token.index < template.length) {
		word = token.sacnText("{{");
		if (word) {
			switch (word[0]) {
				case '#':
					tokenArr.push(["#", word.substr(1)])
					break;
				case '/':
					tokenArr.push(["/", word.substr(1)])
					break;
				default:
					// 智能判断是普通文字的空格,还是标签中的空格
					// 标签中的空格不能去掉,比如<div class="box">不能去掉class前面的空格
					let isInJJH = false;
					// 空白字符串
					var _words = '';
					for (let i = 0; i < word.length; i++) {
						// 判断是否在标签里
						if (word[i] == '<') {
							isInJJH = true;
						} else if (word[i] == '>') {
							isInJJH = false;
						}
						// 如果这项不是空格,拼接上
						if (!/\s/.test(word[i])) {
							_words += word[i];
						} else {
							// 如果这项是空格,只有当它在标签内的时候,才拼接上
							if (isInJJH) {
								_words += ' ';
							}
						}
					}
					tokenArr.push(["text", _words])
					break;
			}
		}
		token.sacnTag("{{")
		word = token.sacnText("}}");
		if (word) {
			switch (word[0]) {
				case '#':
					tokenArr.push(["#", word.substr(1)])
					break;
				case '/':
					tokenArr.push(["/", word.substr(1)])
					break;
				default:
					tokenArr.push(["name", word])
					break;
			}
		}
		token.sacnTag("}}")
	}
	return handleTokens(tokenArr)
}
// 处理循环tokens
function handleTokens(tokens) {
	var resultArr = [];
	var loopArr = []
	var temArr = resultArr;

	tokens.forEach((item, index) => {
		switch (item[0]) {
			case '#':
				temArr.push(item)
				loopArr.push(item)
				temArr = item[2] = []
				break;
			case "/":
				loopArr.pop();
				if (loopArr.length > 0) {
					temArr = loopArr[loopArr.length - 1][2]
				} else {
					temArr = resultArr
				}
				break;
			default:
				temArr.push(item)
				break
		}
	})
	return resultArr
}
// 渲染
function resuleStr(Tokens, data) {
	let resuleStr = ''
	Tokens.forEach((item, index) => {
		if (item[0] == 'text') {
			resuleStr += item[1]
		} else if (item[0] == 'name') {
			resuleStr += lookup(data, item[1])
		} else if (item[0] == '#') {
			resuleStr += arrRender(item[2], data[item[1]])
		}
	})
	return resuleStr
}
// 循环数组处理
function arrRender(template, arrData) {
	let str = ''
	arrData.forEach((item) => {
		str += resuleStr(template, item)
	})
	return str
}
// 解析a.b.c这种
function lookup(data, key) {
	if (key.indexOf('.') !== -1 && key !== '.') {
		return eval('data.' + key)
	} else if (key === '.') {
		return data
	} else {
		return data[key]
	}
}
function render(id,data){
	debugger
	let template = document.getElementById(id).innerHTML
	let Tokens = getTokens(template)
	let innerHTMLStr = resuleStr(Tokens, data)
	document.getElementById(id).innerHTML = innerHTMLStr
}

效果如图

在这里插入图片描述