mustache
mustache.js 是一个简单强大的 JavaScript 模板引擎,使用它可以简化在 js 代码中的 html 编写。
https://cdn.bootcdn.net/ajax/libs/mustache.js/4.2.0/mustache.js
mustache 语法
- mustache 语法: 是"胡子"的意思, 因为嵌入标记像胡子 {{}}:
- mustache 语法, 也被vue 使用。 mustache 语法库是最早的模板引擎库, 比vue 诞生的要早的多。
- 他的底层是实现机制在当时是非常有创造性的, 轰动性的, 为后续模板引擎的发展提供了崭新的思路。
- UMD: 就是浏览器中也可以使用, 在 node 环境中也可以使用。
数据 -> 视图
dom
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<div>
<ul id="list"></ul>
</div>
</body>
<script>
let arr = [
{ name: "张三", age: 13, sex: "男" },
{ name: "李四", age: 14, sex: "女" },
{ name: "王五", age: 12, sex: "男" },
];
let list = document.getElementById('list');
for (let i = 0; i < arr.length; i++) {
let li = document.createElement('li');
li.innerHTML = arr[i].name + arr[i].age;
list.append(li)
}
// 如果dom层级深,非常笨拙,不好维护
</script>
</html>
数组join()方法
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<div>
<ul id="list">
</ul>
</div>
<script>
let arr = [
{ name: "张三", age: 13, sex: "男" },
{ name: "李四", age: 14, sex: "女" },
{ name: "王五", age: 12, sex: "男" },
];
let list = document.getElementById("list");
for (let i = 0; i < arr.length; i++) {
let str = [
"<li>",
"<div>" + arr[i].name + "的信息</div>",
"<div>",
"<p>姓名:" + arr[i].name + "</p>",
"<p>年龄:" + arr[i].age + "</p>",
"<p>性别:" + arr[i].sex + "</p>",
"</div>",
"</li>"
].join("");
list.innerHTML += str
}
// 曾经非常流行
</script>
</body>
</html>
ES6模板字符串
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<div>
<ul id="list"></ul>
</div>
<script>
let arr = [
{ name: "张三", age: 13, sex: "男" },
{ name: "李四", age: 14, sex: "女" },
{ name: "王五", age: 12, sex: "男" },
];
let list = document.getElementById("list");
for (let i = 0; i < arr.length; i++) {
list.innerHTML += `
<li>
<div>${arr[i].name}的信息</div>
<div>
<p>姓名:${arr[i].name}</p>
<p>年龄:${arr[i].age}</p>
<p>性别:${arr[i].sex}</p>
</div>
</li>
`;
}
</script>
</body>
</html>
Mustache
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<div id="container"></div>
<script src="./mustache.js"></script>
<script>
let templateStr = `
<ul>
{{#arr}}
<li>
<div>{{name}}的信息</div>
<div>
<p>姓名:{{name}}</p>
<p>年龄:{{age}}</p>
<p>性别:{{sex}}</p>
</div>
</li>
{{/arr}}
</ul>
`;
let data = {
arr: [
{ name: "张三", age: 13, sex: "男" },
{ name: "李四", age: 14, sex: "女" },
{ name: "王五", age: 12, sex: "男" },
],
};
let domStr = mustache.render(templateStr, data);
let container = document.getElementById('container');
container.innerHTML = domStr;
</script>
</body>
</html>
语法
<script src="./mustache.js"></script>
mustache.render(templateStr, data);
templateStr模板字符串
data渲染的数据
数据结构简单-正则
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<div id="container">
</div>
<script>
// 正则表达式实现简单模板数据填充
let tempStr = "<h1>我买了一个{{phone}},话了{{money}}元,好{{mood}}</h1>";
let data = {
phone: " 手机",
money: 3000,
mood: "后悔",
};
function render(tempStr, data) {
return tempStr.replace(/\{\{(\w+)\}\}/g, function (findStr, $1) {
return data[$1];
});
}
let result = render(tempStr, data);
let container = document.getElementById("container");
container.innerHTML = result;
</script>
</body>
</html>
如果是复杂的模板数据?
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<div id="container"></div>
<script>
let templateStr = `
<ul>
<p>{{title}}</p>
{{#arr}}
<li>
<div>
<p>姓名:{{name}}</p>
<p>年龄:{{age}}</p>
<p>性别:{{sex}}</p>
<p>爱好:
{{#hobbites}}
<span>{{.}}</span>
{{/hobbites}}
</p>
</div>
</li>
{{/arr}}
</ul>
`;
let data = {
title: "个人信息",
arr: [
{
name: "张三",
age: 13,
sex: "男",
hobbites: ["篮球", "羽毛球", "乒乓球"],
},
{
name: "李四",
age: 14,
sex: "女",
hobbites: ["足球", "篮球", "乒乓球"],
},
{
name: "王五",
age: 12,
sex: "男",
hobbites: ["游泳", "桌球", "斯诺克"],
},
],
};
let result = Bysun_TemplateEngine.render(templateStr, data);
let container = document.getElementById("container");
container.innerHTML = result;
</script>
</body>
</html>
index.js
import parseTemplateToTokens from './parseTemplateToTokens.js'
import renderTemplate from './renderTemplate.js'
window.Bysun_TemplateEngine = {
// 渲染方法
render(templateStr, data) {
// 实例化一个扫描器,构造时候提供一个参数,这个参数就是模板字符串
// 也就是说这个扫描器就是处理模板字符串的
let tokens = parseTemplateToTokens(templateStr);
// console.log('tokens',tokens)
let domStr = renderTemplate(tokens,data);
// console.log(domStr,'domStr')
return domStr;
}
}
Scanner.js
// 扫描器
export default class Scanner {
constructor(templateStr) {
// 指针
this.pos = 0;
// 尾巴,初始就是模板字符串
this.tail = templateStr;
this.templateStr = templateStr;
}
// 就是走过指定内容,没有返回值
scan(tag) {
if(this.tail.indexOf(tag) == 0) {
// tag {{ 长度是2,就让指针后移多少位
this.pos += tag.length;
// 改变尾巴,当前指针开始,到结束的全部字符
this.tail = this.templateStr.substr(this.pos)
}
}
// this.templateStr = "我买了一个{{phone}},好{{mood}}";
// 让指针扫描,直到遇见指定内容结束,并且能够返回结束之前路过的文字
scanUnit(stopTag) {
const pos_backup = this.pos; // 0
// 当尾巴的开头不是stopTag的时候,就说明还没有扫描到stopTag
// && 防止找不到,寻找到最后停止下来
while (this.tail.indexOf(stopTag) != 0 && !this.eos()) {
this.pos++
// 改变尾巴,当前指针开始,到结束的全部字符
this.tail = this.templateStr.substring(this.pos)
}
return this.templateStr.substring(pos_backup, this.pos)
}
// 指针是否到头
eos() {
return this.pos >= this.templateStr.length
}
}
parseTemplateToTokens.js
import Scanner from './Scanner.js';
import nestToken from './nestTokens.js'
export default function parseTemplateToTokens(templateStr) {
let tokens = [];
// 创建扫描器
let scanner = new Scanner(templateStr);
let words;
while (!scanner.eos()) {
//收集开始标记{{之前的文字
words = scanner.scanUnit('{{');
// 存入数组
if (words != '') {
let _word = "";
let flag = false;
for (let i = 0; i < words.length; i++) {
const temp = words[i];
if (temp === "<") {
flag = true
} else if (temp === ">") {
flag = false
}
// 标签中的样式空格要保留 <p class="bd"></p>
if (/\s/.test(temp)) {
if (flag) {
_word += temp
}
} else {
_word += temp
}
}
tokens.push(['text', _word])
}
// 过{{
scanner.scan('{{')
//收集开始标记}}之前的文字
words = scanner.scanUnit('}}');
// 存入数组
if (words != '') {
// 当模板字符串层级很深,数据结构有很多子项,且子项也是数组
if (words[0] == '#') {
tokens.push(['#', words.substr(1)])
} else if (words[0] == '/') {
tokens.push(['/', words.substr(1)])
} else {
tokens.push(['name', words]);
}
}
// 过{{
scanner.scan('}}')
}
// console.log('---', tokens)
return nestToken(tokens);
}
nestTokens.js
export default function nestToken(tokens){
let nestTokens = []; // 返回数据
let sections = [];
// 收集器,收集子孙元素,指向nestTokens数组,引用类型值指向的是同一个数组
// 遇见#,收集器会遇到当前token的下标为2的新数组
let collector = nestTokens;
// 栈结构,存放小tokens, 栈顶(靠近端口的,最新进入的)tokens数组中前操作的这个tokens小数组
tokens.forEach((token,index) => {
switch (token[0]) {
case '#':
collector.push(token); // 收集器放入这个token
sections.push(token); // 入栈
collector = token[2]= []; // 给token 添加下标为2的项目,并让收集器指向它
break
case '/':
sections.pop(); // 出栈 pop会返回刚刚弹出的项
// 改变收集器为栈结构队尾(队尾就是栈顶) 那项下标为2的数组
collector = sections.length > 0 ? sections[sections.length-1][2] : nestTokens
break
default:
collector.push(token)
break
}
})
return nestTokens
}
renderTemplate.js
// tokens转为字符串
export default function renderTemplate(tokens, data) {
let res = '';
for (let i = 0; i < tokens.length; i++) {
let token = tokens[i];
if (token[0] == 'text') {
res += token[1];
} else if (token[0] == 'name') {
// 如果是name,需要处理 a.b.c
res += handle(data, token[1])
} else if (token[0] == '#') {
// 数据类型 ['#','aaaa',['xxxx']] 需要调用renderTemplate方法处理
// 数组要解析处理,需要循环然后调用renderTemplate方法
res += handleArray(token, data)
}
}
return res;
}
// 处理 数组中 name 为 a.b.c 的变量
function handle(dataObj, keyName) {
/*
let a = {
b: {
c: 1,
}
}
dataObj = { a }
keyName = 'a.b.c'
*/
if (keyName.indexOf('.') !== -1 && keyName !== ".") {
let temp = dataObj
let keys = keyName.split('.') // ['a','b','c']
for (let i = 0; i < keys.length; i++) {
// console.log(keys[i])
// console.log(temp[keys[i]])
/*
1 keys[i] = a; temp[keys[i]] = {b:{c:1}};
2 keys[i] = b; temp[keys[i]] = {c:1};
3 keys[i] = c; temp[keys[i]] = 1;
*/
temp = temp[keys[i]]
}
return temp
}
// 没有.,则返回
return dataObj[keyName];
}
function handleArray(token, data) {
let res = ''
// 数据类型 token = ['#','aaaa',['xxxx']] 需要调用renderTemplate方法处理
let list = handle(data, token[1]);
if (list && list.length) {
for (let i = 0; i < list.length; i++) {
res += renderTemplate(token[2], {
...list[i],
'.': list[i]
})
}
}
return res
}