本篇笔记来自于尚硅谷——Vue源码解析系列课程。
Mustache
Vue中的Mustache就是Vue中的模板引擎,另外v-for
指令也是一种模板引擎的应用。
模板引擎是将数据变为视图的最优雅的解决方案。
数据:
[
{"name":"小明", "age":12, "sex":"男"},
{"name":"小红", "age":11, "sex":"女"},
{"name":"小强", "age":13, "sex":"男"}
]
复制代码
视图:
<ul>
<li>
<div class="hd">小明的基本信息</div>
<div class="bd">
<p>姓名:小明</p>
<p>年龄:12</p>
<p>性别:男</p>
</div>
</li>
<li>...</li>
</ul>
复制代码
历史上的方案
-
纯DOM法:非常笨拙,没有实战价值
-
数组
join
法:曾几何时非常流行,是曾经的前端必备知识 -
ES6的反引号法:ES6中新增的
'${a}'
语法糖(反引号显示失败,这里用单引号替代),很好用 -
模板引擎:数据变为视图的最优雅的解决方案
纯DOM法
<body>
<ul id="list"></ul>
<script>
let data = [
{"name":"小明", "age":12, "sex":"男"},
{"name":"小红", "age":11, "sex":"女"},
{"name":"小强", "age":13, "sex":"男"}
]
let list = document.querySelector('#list')
//每次循环生成一次DOM,这里只是简单生成,若实现上面的视图,还要创建更多的DOM,很麻烦
for(let obj of data){
for(let item in obj){
let tmp = document.createElement('li')
tmp.innerText=obj[item]
list.appendChild(tmp)
}
//分割线为了美观
list.appendChild(document.createElement('hr'))
}
</script>
</body>
复制代码
数组join法
JS创建DOM的方法还可以使用html
字符串,但是在JS中的传统字符串是不能换行的,如下:
let str = 'asdasd
asdasd
asdasd'
复制代码
这样就导致了,在代码中无法清晰看出层级关系,可读性很差。
ES6中的出现使用反引号的模板字符串在曾经还未出现,但是数据中的元素在代码中是可以换行的,所以使用的是数组join
方法来实现类似的效果:
let arr = ['A',
'B',
'C',
'D']
let str = arr.join('')
consloe.log(str)//ABCD
复制代码
实现
<body>
<ul id="list"></ul>
<script>
let data = [
{"name":"小明", "age":12, "sex":"男"},
{"name":"小红", "age":11, "sex":"女"},
{"name":"小强", "age":13, "sex":"男"}
]
let list = document.querySelector('#list')
////将数据与字符串拼接
for(let obj of data){
//代码层级非常清晰,获得这样的代码可以先在html中编辑好,再利用编辑器的多行编辑
list.innerHTML+=[
'<li>',
' <div class="hd">'+obj.name+'的基本信息</div>',
' <div class="bd">',
' <p>姓名:'+obj.name+'</p>',
' <p>年龄:'+obj.age+'</p>',
' <p>性别:'+obj.sex+'</p>',
' </div>',
'</li>',
].join('')
//分割线为了美观
list.appendChild(document.createElement('hr'))
}
</script>
</body>
复制代码
ES6反引号法
<body>
<ul id="list"></ul>
<script>
let data = [
{"name":"小明", "age":12, "sex":"男"},
{"name":"小红", "age":11, "sex":"女"},
{"name":"小强", "age":13, "sex":"男"}
]
let list = document.querySelector('#list')
for(let obj of data){
//ES6语法中的模板字符串可以直接换行,并且可以用${}拼接
list.innerHTML+=`
<li>
<div class="hd">${obj.name}的基本信息</div>
<div class="bd">
<p>姓名:${obj.name}</p>
<p>年龄:${obj.age}</p>
<p>性别:${obj.sex}</p>
</div>
</li>`
//分割线为了美观
list.appendChild(document.createElement('hr'))
}
</script>
</body>
复制代码
效果与上面相同。
基本使用
- Mustache官方git:github.com/janl/mustac…
- Mustache,胡子,因为它的嵌入标记
{{}}
非常像胡子 {{}}
语法也被Vue沿用- Mustache是最早的模板引擎库,比Vue诞生的还要早得多,它的底层实现机理在当时是非常有创造性的,轰动性的,为后续的模板引擎发展提供了崭新的思路
这里只讲基本使用,不使用npm和node进行安装和使用,直接使用引用编译好的库或者CDN链接即可。
这里使用的是Mustache4.0.1
<script src="https://cdn.bootcdn.net/ajax/libs/mustache.js/4.0.1/mustache.js"></script>
复制代码
引用后,这个库会提供一个名为Mustache
的全局变量。
最重要的方法Mustache.render(templateStr,data)
,第一个参数放模板字符串,第二个参数为渲染用的数据。
-
普通对象
<body> <script src="./mustache-4.0.1.js"></script> <div id="box"></div> <script> let data={ OS:'鸿蒙', mood:'好' } let html = '今天手机安装了{{OS}}系统,心情变得真{{mood}}' let box = document.querySelector('#box') box.innerHTML=Mustache.render(html,data) </script> </body> 复制代码
-
循环数组
<script src="./mustache-4.0.1.js"></script> <ul id="list"></ul> <script></script> </body> 复制代码
-
布尔值
<body> <script src="./mustache-4.0.1.js"></script> <div id="box"></div> <script> let data={ show : true } //使用布尔值表示是否显示包含的元素,false不显示,反而反之。 let html= ` {{#show}} <h1>猜猜我会不会出现?</h1> {{/show}} ` let box = document.querySelector('#box') //data必须是一个包含arr数组的对象,而不是arr数组对象本身 box.innerHTML=Mustache.render(html,data) </script> </body> 复制代码
-
script标签内容作为模板
<body> <script src="./mustache-4.0.1.js"></script> <!-- type只要不被浏览器认识即可,这样用既可以看清元素层级,还可以继续使用编辑器的Emmet功能快速编辑 --> <script type="text/template" id="myTemplate"></script> <ul id="list"></ul> <script> let data = { arr:[ {"name":"小明", "age":12, "sex":"男","hobbies":["篮球","编程"]}, {"name":"小红", "age":11, "sex":"女","hobbies":["唱歌"]}, {"name":"小强", "age":13, "sex":"男","hobbies":[]} ] } let list = document.querySelector('#list') let html = document.querySelector('#myTemplate').innerHTML list.innerHTML=Mustache.render(html,data) </script> </body> 复制代码
Mustache不支持任何运算和表达式如{{1+1}}等
底层核心机理
一张图可以概括Mustache的机理
Mustache底层只做两件事情
- 将模板字符串编译为
tokens
形式 - 将
tokens
结合数据,解析为DOM字符串
简单正则表达式
Mustache库并非是用正则表达式实现的,但是简单的Mustache功能可以通过正则表达式实现。
<body>
<div id="box"></div>
<script>
let html = '我今天学习了{{knowledge}}的底层原理,虽然有点{{level}},但是很{{mood}}'
let data = {
knowledge: 'Mustache',
level: '难',
mood: '开心'
}
function render(templateStr,data){
//str.replace(正则表达式,替换字符,可以是函数的返回值)
// (\w+)的括号用于捕获内容
// /g全局模式,不然只会替换匹配的第一个
// 回调函数,findStr:匹配到的字符串如{{knowledge}},$1:捕获到的字符串如knowledge,$2和$3(这里没用上)分别为匹配到的下标和整个templateStr字符串本身
return templateStr.replace(/\{\{(\w+)\}\}/g,function(findStr,$1){
return data[$1]
})
}
document.querySelector('#box').innerText = render(html,data)//我今天学习了Mustache的底层原理,虽然有点难,但是很开心
</script>
</body>
复制代码
Token思想
简单正则表达式的实现算一个餐前小菜,前面概括图中的模板字符串,已经不陌生了,但是编译过后的tokens
是什么呢?
tokens
是一个JS数组,说白了,就是模板字符串的JS表示,它是抽象语法树(AST),虚拟DOM等等的开山鼻祖。
在Mustache中,模板字符串将会被进行以下抽象:
这里转换出来的tokens
是一个二维数组,通过观察不难发现,模板字符串以{{
和}}
为界限被分割成不同的部分。{{}}
外的字符串被命名为text
,{{}}
内的字符串被命名为name
。它们的值都被放在数组的第二位。并且每一段都会作为一个数组对象,这个数组对象称为token
,包含这些数组对象的数组称为tokens
。
当有循环情况下的模板字符串,会被转换成嵌套更深的tokens
:
循环会变成一个新的类型#
,数组第二位为循环数组的变量名,第三位又是一个tokens
。
双重循环下模板字符串,转换后的tokens
,嵌套更深:
其中的null
为匹配位置的开始索引到结束索引,不做校验,没有任何用处。
暴露源码tokens
因为Mustache库直接做好了编译tokens
和数据结合,并没有直接将tokens
直接展示。所以需要改动一下源代码,使我们能够看到真正tokens
是什么样的。
这是这里使用的版本:cdn.bootcdn.net/ajax/libs/m… ,打开后浏览器可以直接看,但是浏览器观看可读性较差,建议另存为或复制到本地,在编辑器中查看。
需要修改的源码位置在parseTemplate
方法的结尾处。
function parseTemplate (template, tags) {
...
return nestTokens(squashTokens(tokens));
}
复制代码
改为:
function parseTemplate (template, tags) {
var tokens = nestTokens(squashTokens(tokens));
console.log(tokens);
return tokens;
}
复制代码
这样每当使用Mustache时,可以自动在控制台输出tokens
。
再次运行前面基本使用的例子1和4,查看控制台:
其中的数字为匹配位置的开始索引到结束索引,不做校验,没有任何用处。