模板引擎的定义
模板引擎就是将数据变为视图最优雅的解决方案
例如:VUE的v-for、mustache
历史上数据变为视图方法:
- 纯DOM方法
- 数组join
- ES6的反引号法:``${a} `
- 模板引擎
mustache的基本使用
官方git:https://github.com/janl/mustache.js
例子:
let arr = [
{ name: "小红", sex: "女", age: 18 },
{ name: "小黑", sex: "男", age: 19 },
{ name: "小白", sex: "女", age: 17 },
];
1.引入mustache库
2.语法
<ul>
{{#arr}}
<li>
<div class="hd">{{name}}的基本信息</div>
<div class="bd">
<p>姓名:{{name}}</p>
<p>性别:{{sex}}</p>
<p>年龄:{{age}}</p>
</div>
</li>
{{/arr}}
</ul>
Mustache.render(templateStr,data)
手写原理代码(简化版)
没有包含布尔值,其它基本实现
index:
import parseTempalteToTokens from "./parseTempalteToTOkens.js";
import renderTamplate from "./renderTamplate.js";
Window.templateEngine = {
render(templateStr, data) {
let tokens = parseTempalteToTokens(templateStr);
// console.log(tokens);
// 调用renderTemplate函数,让tokens数组变为dom字符串
let domStr = renderTamplate(tokens, data);
return domStr
},
};
parseTempalteToTokens:
import Scanner from "./Scanner";
import nestTokens from "./nestTokens";
/**
*
* 将模板字符串变为token数组
*
*/
export default function parseTempalteToTokens(templateStr) {
let tokens = [];
// 创建扫描器
let scanner = new Scanner(twmplateStr);
let words;
// scanner没有到头
while (!scanner.eos()) {
// 收集开始标记之前的文字
words = scanner.scanUtil("{{");
// 存储
if (words != " ") {
// 普通文字中的空格没有意义;标签中的空格保留<div class="box"></div>
let isInJJH = false;
let _words = "";
for (let i = 0; i < words.length; i++) {
if (words[i] == "<") {
isInJJH = true;
} else {
isInJJH = false;
}
// 如果不是空格,拼接
if (!/\s/.test(words[i])) {
_words += words[i];
} else {
// 如果是空格且在标签内,拼接
if (isInJJH) {
_words += " ";
}
}
}
tokens.push(["text", words]);
}
// 跳过双大括号
scanner.scan("{{");
// 收集}}之前的文字
words = scanner.scanUtil("}}");
// 存储,此时的words为{{}}中的内容
if (words != "") {
if (words[0] == "#") {
tokens.push(["#", words.substring(1)]);
} else if (words[0] == "/") {
tokens.push(["/", words.substring(1)]);
} else {
tokens.push(["name", words.substring(1)]);
}
}
// 跳过双大括号
scanner.scan("}}");
}
return nestTokens(tokens);
}
Scanner:
/**
* 扫描器类
*/
export default class Scannner {
constructor(templateStr) {
this.templateStr = templateStr;
console.log(templateStr);
// 指针
this.pos = 0;
// 尾巴指针
this.tail = templateStr;
}
// 走过指定内容
scan(tag) {
if (this.tail.indexOf(tag) == 0) {
// tag有多长,就让pos后移多少位
this.pos += tag.length;
// 改变尾巴为从当前到最后
this.tail = this.templateStr.substring(this.pos);
}
}
// 让指针进行扫描,直到遇到指定内容,并返回结束之前的文字
scanUtil(stopTag) {
// 记录执行方法的pos的值
let pos_backyp = this.pos;
// 寻找标记且没到头
while (this.tail.indexOf(stopTag) && !this.eos()) {
this.pos++;
// 改变尾巴为从当前到最后
this.tail = this.templateStr.substr(this.pos);
}
return this.templateStr.substring(pos_backyp, this.pos);
}
// 指针是否到头,返回布尔值
eos() {
return this.pos >= this.templateStr.length;
}
}
nestTokens:
/**
* 折叠,将#和/ 之间的tokens整合起来作为它下标为3的向
* 将零散的tokens串起来--->栈:遇见#入栈;遇见/出栈
*/
export default function nestTokens(tokens) {
// 结果数组
let nestedTokens = [];
// 收集器,注意收集器会变化
let collector = nestedTokens;
// 栈结构
let sections = [];
for (let i = 0; i < tokens.length; i++) {
let token = tokens[i];
switch (token[0]) {
case "#":
// 收集器存储token
collector.push(token);
// 入栈
sections.push(token);
// 收集器要换人
collector = token[2] = [];
break;
case "/":
// 出战
sections.pop();
// 改变收集器为上一层
collector =
sections.length > 0 ? sections[sections.length - 1][2] : nestedTokens;
break;
default:
collector.push(token);
}
}
return nestedTokens;
}
renderTamplate:
import lookup from "./lookup.js";
import parseArray from "./parseArray.js";
/**
* 让tokens数组变为dom字符串
* 递归:#标记的tokens需要处理它下标为1
*/
export default function renderTamplate(tokens, data) {
// 结果字符串
let resultStr = "";
// 遍历tokens
for (let i = 0, len = tokens.length; i < len; i++) {
let token = tokens[i];
// 看类型
if (token[0] == "text") {
resultStr += token[1];
} else if (token[0] == "name") {
// 如果试name类型,直接使用它的值,要调用lookup
// 防止是a.d.c
resultStr += lookup(data, token[1]);
} else if (token[0] == "#") {
resultStr += parseArray(token, data);
}
}
return resultStr;
}
parseArray:
import lookup from "./lookup.js";
import renderTemplate from "./renderTamplate.js";
/**
* 处理token数组,结合renderTemplate实现递归
* 注意:接收参数为token
* ['#',student,[]]
*
*
* 这个函数:递归调用renderTemplate,次数有data决定
*
*/
export default function parseArray(token, data) {
// 得到data中数组需要渲染的部分
let v = lookup(data, token[1]);
// 结果字符串
let resultStr = "";
// 遍历v,这里只考虑v为数组的情况
// 重点:下面这个循环!遍历的是数据
for (let i = 0, len = v.length; i < len; i++) {
// '.'的识别,例子见最后
resultStr += renderTemplate(token[2], {
...v[i],
".": v[i],
});
}
return resultStr;
}
/** .的例子
* <ul>
{{#arr}}
<li>
{{.}}
</li>
{{/arr}}
</ul>
*
* let data={
* arr:['1','2','3']
* }
*
*
*/
lookup:
/**
* 让dataObj对象中可以用.符号的keyName属性
* 例如:dataObj为:
* {
* a:{
* b:{
* c:100
* }
* }
* }
* 那么lookup(dataObj,'a.b.c')结果为100
*/
export default function lookup(dataObj, keyName) {
// 看看keyName中有没有.符号,但keyname不能为.
if (keyName.indexOf(".") != -1 && keyName != ".") {
// 如果有,拆分属性
let keys = keyName.split(".");
// 设置临时变量tmp,用于周转,一层层的找
let tmp = dataObj;
for (let i = 0, len = keys.length; i < len; i++) {
// 更新tmp
tmp = tmp[keys[i]];
}
return tmp;
}
return dataObj[keyName];
}