Vue之mustache模板引擎笔记记录
参考
- 尚硅谷邵山欢(考拉老师)Vue之mustache模板引擎
- 为什么要学Vue源码
- Vue组件由哪三部分组成?
- Vue父子组件如何通信?
- Vuex的作用是什么?
- Vue是怎么实现双向绑定的?
- Vue的最小化更新过程是怎样的?
- Vue如何实现指令系统的?
- 中高级前端、leader职位必会底层知识
- 为企业“造轮子”,开发通用组件
- 解决编程中遇见的问题
- 解决效率问题
- vue源码非常庞大,各种机理很多:模板技术、数据劫持、虚拟节点、最小量更新、抽象语法树……
- 手写底层源码,拒绝纸上谈兵,让同学能实打实的提升编程
- 核心机理是共通的、永恒的。
历史上曾经出现的数据变为视图的方法
-
纯DOM法:非常笨拙,没有实战价值
-
数组join法: 曾几何时非常流行,是曾经的前端必会知识
-
ES6的反引号法:ES6中新增的
${a}
语法糖,很好用 -
模板引擎:解决数据变为视图的最优雅的方法
-
纯DOM法
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>mustache</title>
</head>
<body>
<ul id="list"></ul>
<script>
var arr = [
{"name": "小明", "age":12,"sex":"男"},
{"name": "小红", "age":13,"sex":"女"},
{"name": "小强", "age":14,"sex":"男"}
]
var list = document.getElementById('list')
for (var i=0,len = arr.length;i<len;++i){
// 遍历每一项,都需要的DOM的方法创建li标签
var oli = document.createElement("li")
var hdDiv = document.createElement("div")
hdDiv.className = 'hd'
hdDiv.innerText = arr[i].name + '的基本信息'
var bdDiv = document.createElement('div')
bdDiv.className = 'bd'
var p1 = document.createElement('p')
p1.innerHTML = '姓名: ' + arr[i].name
bdDiv.appendChild(p1)
var p2 = document.createElement('p')
p2.innerHTML = '年龄: ' + arr[i].age
bdDiv.appendChild(p2)
var p3 = document.createElement('p')
p3.innerHTML = '性别: ' + arr[i].sex
bdDiv.appendChild(p3)
// 创建的节点是孤儿节点,所以必须要上DOM树才能被用户看见
oli.appendChild(hdDiv)
oli.appendChild(bdDiv)
list.appendChild(oli)
}
</script>
</body>
</html>
- 数组join方法 , html那块的文本有格式化的感觉
for (var i=0,len = arr.length;i<len;++i){
list.innerHTML += [
'<li>',
' <div class="hd">' +arr[i].name + '的基本信息</div>',
' <div class="bd">',
' <p>姓名:'+arr[i].name+'</p>',
' <p>年龄:'+arr[i].age+'</p>',
' <p>性别:'+arr[i].sex+'</p>',
' </div>',
'</li>'
].join('')
}
- ES6 反引号
for (var i=0,len = arr.length;i<len;++i){
list.innerHTML += `
<li>
<div class="hd">${arr[i].name}的基本信息</div>
<div class="bd">
<p>姓名:${arr[i].name}</p>
<p>年龄:${arr[i].age}</p>
<p>性别:${arr[i].sex}</p>
</div>
</li>
`
}
mustache
- mustache官方git
- mustache是“胡子”的意思,因为它的嵌入标记
{{ }}
非常像胡子,也被Vue沿用 - mustache是最早的模板引擎库,比Vue诞生的早多了,它的底层实现机理在当时是非常有创造性的、轰动性的,为后续模板引擎的发展提供了崭新的思路
- bootcdn加速
<body>
<ul id="list"> </ul>
<script src="https://cdn.bootcdn.net/ajax/libs/mustache.js/4.1.0/mustache.js"></script>
<script type ="data" id="tmpstr">
{{#arr}}
<li>
<div class="hd">{{name}}的基本信息</div>
<div class="bd">
<p>姓名:{{name}}</p>
<p>年龄:{{age}}</p>
<p>性别:{{sex}}</p>
</div>
</li>
{{/arr}}
</script>
<script>
var data = {
arr: [
{"name": "小明", "age":12,"sex":"男"},
{"name": "小红", "age":13,"sex":"女"},
{"name": "小强", "age":14,"sex":"男"}
]
}
var list = document.getElementById('list')
var tmpstr = document.getElementById('tmpstr').innerText ;// 这样把模板放到一个script (type 不能是能执行的脚本类型就行)
document.getElementById('list').innerHTML = Mustache.render(tmpstr,data)
</script>
</body>
正则表达式
- 在较为简单的示例情况下,可以用正则表达式实现
- 最简单的模板引擎的实现机理,利用的是正则表达式中的replace方法
function render(templateStr, data) {
return templateStr.replace(/\{\{(\w+)\}\}/g,function(findStr, $1){
return data[$1]
})
自定义
- mustache库机理
- 模块化打包工具有webpack (webpack-dev-server)、rollup、Parcel等
- mustache官方库使用rollup进行模块化打包, 使用webpack(webpack-dev-server)进行模块化打包,能让我们更方便地在浏览器中(而不是nodejs环境中)实时调试程序,相比nodejs控制台,浏览器控制台更好用,比如能够点击展开数组的每项。
- 生成库是UMD的,这意味着它可以同时在nodejs环境中使用,也可以在浏览器环境中使用。实现UMD不难,只需要一个“通用头”即可。
function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
typeof define === 'function' && define.amd ? define(factory) :
(global = global || self, global.Mustache = factory());
}(this, function () {})
mustache.ts
可以模块化开发
class Scanner {
pos: number // 指针
tail: string // 尾巴,一开始就是模板字符串原文
templateStr: string // 模板字符串
constructor(templateStr: string){
this.pos = 0
this.tail = templateStr
this.templateStr = templateStr
}
scan(stopTag: string):void {
if (this.tail.indexOf(stopTag) === 0) {
this.pos += stopTag.length
this.tail = this.templateStr.substring(this.pos);
}
} // 走过指定的内容,没有返回值
// 让指针进行扫描,直到遇见指定的内容结束,并且能够返回结束之前路过的文字
scanUtil(stopTag: string): string {
const pos_backup = this.pos
// 当尾巴的开头不是stopTag 的时候,就说明还没有扫描到stopTag
while(this.tail.indexOf(stopTag) !==0 && !this.eos()){
this.pos++;
// 改变尾巴从当前指针的这个字符开始到最后
this.tail = this.templateStr.substr(this.pos)
}
return this.templateStr.substring(pos_backup, this.pos)
}
// 判断指针是否走到了头
eos(): boolean{
return this.pos >= this.templateStr.length
}
}
function nestTokens(tokens) {
let nestedTokens = [] // 结果数组
let sections = [] // 一个栈结构
let collector = nestedTokens // 收集器
for (let i=0,len = tokens.length ;i<len ;++i) {
let token = tokens[i]
switch (token[0]) {
case '#':
collector.push(token)
sections.push(token)
collector = token[2] = [] // 给token 添加下标为2的项 并且让收集器指向它
break
case '/':
sections.pop()
collector = sections.length >0 ? sections[sections.length - 1][2]:nestedTokens
break
default:
collector.push(token)
}
}
return nestedTokens
}
function parseTemplateToTokens(templateStr: string) {
let tokens = []
let scanner = new Scanner(templateStr)
let words:string
while(!scanner.eos()){
words = scanner.scanUtil('{{')
if (words !== '' ){
tokens.push(['text',words.replace(/\s+/g,' ')]) // 将多个空格合并成一个
}
scanner.scan('{{')
words = scanner.scanUtil('}}')
if (words!==''){
if (words[0]==='#'){
tokens.push(['#', words.substring(1)])
} else if (words[0] === '/') {
tokens.push(['/', words.substring(1)])
} else {
tokens.push(['name', words])
}
}
scanner.scan('}}')
}
return nestTokens(tokens)
} // 将模板字符串转成tokens数组
function lookup (dataObj, keyName:string) {
// 判断的时候不能是. 本身
if (keyName.indexOf('.') !== -1 && keyName !== '.') {
let keys = keyName.split('.')
let temp = dataObj
for (let i=0, len = keys.length; i<len ; ++i) {
temp = temp[keys[i]]
}
return temp
}
return dataObj[keyName] // 没有'.'
} // 处理级联属性 'a.b'
function parseArray(token, data) {
let v = lookup(data, token[1]) as []
let resultStr = ''
for (let i=0, len = v.length ;i<len ;++i) {
resultStr += renderTemplate(token[2], {
...(v[i] as {}),
'.': v[i]
})
}
return resultStr
}// 处理数组渲染
function renderTemplate (tokens:[], data:{} | []) {
let resultStr = ''
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') {
resultStr += lookup (data, token[1] as string)
} else if (token[0] === '#') {
resultStr += parseArray(token, data)
}
}
return resultStr
} // 将tokens数组转成dom字符串
function render (templateStr, data) {
let tokens = parseTemplateToTokens(templateStr)
console.log(tokens)
let result = renderTemplate(tokens,data)
console.log (result)
return result
}
- index.html
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>mustache</title>
</head>
<body>
<div id ="container">
</div>
<script src="TS/mustache.js"></script>
<script>
var templateStr = `
<ol>
{{#students}}
<li>
学生{{name}} 好朋友是{{friend.name}} 自己的爱好是
<ol>
{{#hobbies}}
<li>{{.}}</li>
{{/hobbies}}
</ol>
</li>
{{/students}}
</ol>
`
var data = {
students: [
{name: "小明", hobbies: ['编程', '打游戏'], friend: {name: '小七'}},
{name: "小红", hobbies: ['追剧'], friend: {name: '小紫'}}
]
}
document.getElementById("container").innerHTML = render(templateStr,data)
</script>
</body>
</html>