mustache 基本语法
mustache是胡子的意思,他的标记{{}}
非常像胡子 所以就叫这个名字,这个语法也被vue
沿用,但是他不能和vue
一样在里边写表达式。
下边使用的都是简化版的mustache
也就是最后写出来的样子。原版直达链接->https://github.com/janl/mustache.js
渲染变量
1 2 3 4 5 6
| const data = { name:'Joker', age:18 } const templateStr = `我叫{{name}},今年{{age}}岁`; document.body.innerHTML = Mustache.render(templateStr,data);
|
渲染简单数组
1 2 3 4 5 6 7 8 9 10 11
| const data = { user:['Joker','Pink','xyooio'] } const templateStr = ` <ul> {{#user}} <li>{{.}}</li> {{/user}} </ul> `; document.body.innerHTML = Mustache.render(templateStr,data);
|
渲染对象数组
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| const data = { user: [ { name: 'Joker', age: 18 }, { name: 'Pink', age: 19 }, { name: 'xyooio', age: 20 }, ] } const templateStr = ` <ul> {{#user}} <li>我叫{{name}},今年{{age}}岁</li> {{/user}} </ul> `; document.body.innerHTML = Mustache.render(templateStr,data);
|
mustache的底层核心
根据上边写的基本语法就可以看出来,Mustache
就是将我们的模板变成结合数据变为模板字符串。而这个过程中必然不是直接转化的 哈哈哈哈,会有一个中间过程,它最后产出的东西叫做tokens
。
那么他的流程就是 将模板字符串编译为tokens,然后再结合数据将tokens
解析成dom字符串
token是什么样子呢
1
| const templateStr = `我叫{{name}},今年{{age}}岁`;
|
这样的模板字符串解析为编译为tokens之后的样子是这样的。
1 2 3 4 5 6 7
| [ ["text","我叫"], ["name","name"], ["text",",今年"], ["name","age"], ["text","岁"] ]
|
可以看到他是一个二维数组, 第一项是类型,第二项是要替换的值。text直接放进去,name就读取数据源了的值
- text:包括标签和换行空格等,只要不是在
{{}}
里都在这块 - name:代表要替换的基础数据
- #:循环数组,从#到/之间的部分都为这一个行
看一个循环的tokens,第三项就是要循环的tokens
了
1 2 3 4 5 6 7 8 9 10 11
| [ ["text","<ul>"], ["#","user", [ ["text","<li>"], ["name","."], ["text","</li>"] ] ], ["text","</ul>"] ]
|
mustache的实现
创建项目
简单一点,不用webpack创建了,就简单的创建几个文件就行了
创建一个目录Mustache
;
创建Mustache/index.html
文件 用来引用js,来看效果
编写如下内容,简单的引入无其他
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| <!DOCTYPE html> <html lang="en">
<head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title>
</head>
<body>
</body> <script type="module"> import Mustache from "./src/index.js"; </script>
</html>
|
创建Mustache/src/index.js文件
项目的入口文件
编写如下内容,简单的抛出无其他
1 2 3 4 5
| export default { render(templateStr, data) { } }
|
思路
看着上边的流程就可以看出来 需要基本需要编写两个函数1. 将模板字符串编译为tokens
的函数,2. 将tokens
解析为dom字符串的函数。
- 看第一个函数,将模板编译为tokens,那就需要一个扫描器,也就是是我们一样的眼睛 哈哈哈,一个一个的去看(实现
scanner
类)- 属性
- pos 指针 记录现在看到第几个字符了
- tail 当前指针本身以及之后的字符串
- templateStr 模板字符串
- 方法
- scan 跳动指针,用于跳过标识
- scanUntil 传入一个标识,当指针走到这个位置了停止扫描,然后返回走过的内容
- eos 指针是不是已经走完了整个字符串
scanner
类已经给我们返回我们需要的内容了,那下来根据这些内容生成tokens
数组- 上边的数组是没有将循环的
tokens
折叠进第三项的,所以还需要一个方法去折叠 - 将
tokens
合并数据解析为dom字符串
实现scanner
类
创建Mustache/src/Scanner.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| export default class Scanner{ constructor(templateStr) { this.templateStr = templateStr; this.pos = 0; this.tail = templateStr; }
scan(tag){ this.pos += tag.length; this.tail = this.templateStr.substring(this.pos); }
scanUntil(tag){ const startPos = this.pos; while(this.eos() || this.tail.indexOf(tag) !== 0){ this.pos++; this.tail = this.templateStr.substring(this.pos); } return this.templateStr.substring(startPos,this.pos); } eos(){ return this.pos >= this.templateStr.length; } }
|
实现模板转tokens
上边的Scanner
类已经可以找跳过指定字符也可以获取到两个标记之间的内容了,那下来就简单了 循环读取跳过就好了呀。
创建Mustache/src/toTokens.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| import Scanner from "./Scanner.js";
export default function toTokens(templateStr){ const scanner = new Scanner(templateStr); const tokens = []; let word = ''; while (!scanner.eso()){ word = scanner.scanUntil('{{'); word && tokens.push(['text',word]); scanner.scan('{{'); word = scanner.scanUntil('}}');
if(word){ if(word[0] === '#'){ tokens.push(['#',word.substring(1)]); }else if(word[0] === '/'){ tokens.push(['/',word.substring(1)]); }else { tokens.push(['name',word]); } } scanner.scan('}}'); } return tokens; }
|
按顺序的话 应该是在写Scanner类之前写这块的代码 哈哈哈哈
现在的话 已经可以看到初步的token了 如果没有循环的话 再加上后边我们要写的 解析dom的方法 已经可以处理了。
折叠我们循环的部分
在Mustache/src/toTokens.js
创建nestTokens
方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| function nestTokens(tokens){ const nestedTokens = []; let collector = nestedTokens; const sections = []; for(const token of tokens){ if(token[0] === '#'){ sections.push(token); collector.push(token); collector = token[2] = []; }else if(token[0] === '/'){ sections.pop(); collector = sections.length === 0 ? nestedTokens : sections[sections.length - 1][2]; }else{ collector.push(token); } } return nestedTokens; }
|
然后再修改toTokens
方法
1
| return nestTokens(tokens);
|
解析为dom字符串
创建Mustache/src/renderTemplate.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| export default function renderTemplate(tokens,data){ let resultDom = ''; for(const token of tokens){ if(token[0] === 'text'){ resultDom += token[1]; }else if(token[0] === 'name'){ if(token[1] === '.'){ resultDom += data; continue; } resultDom += eval('data.' + token[1]); }else if(token[0] === '#'){ const arr = eval('data.' + token[1]); for(const item of arr){ resultDom += renderTemplate(token[2],item) } } } return resultDom; }
|
这块用的eval
方法很不安全 哈哈哈 后边再更新这块的写法
最后的修改
修改 Mustache/src/index.js
文件
1 2 3 4 5 6 7 8
| import toTokens from "./toTokens.js"; import renderTemplate from "./renderTemplate.js"; export default { render(templateStr, data,selector) { const tokens = toTokens(templateStr); document.querySelector(selector).innerHTML = renderTemplate(tokens,data) } }
|
测试一下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| import Mustache from "./src/index.js"; const data = { user: [ { name: 'Joker', age: 18,hobbies:['javascript','vue'] }, { name: 'Pink', age: 19,hobbies:['王者荣耀','吃鸡'] }, { name: 'xyooio', age: 20,hobbies:['双人成行','分手厨房'] }, ] } const templateStr = ` <ul> {{#user}} <li> 我叫{{name}},今年{{age}}岁,我的爱好都有 <ol> {{#hobbies}} <li>{{.}}</li> {{/hobbies}} </ol> </li> {{/user}} </ul> `; Mustache.render(templateStr,data,'body')
|