模板引擎是将数据变成视图的最优雅的方式
没使用vue的v-for,了解前端的渲染发展历程
1.使用原生要先获取dom节点然后appendChild
2.es6的模板字符串
3.mustache是一个js库,使用语法:mustache.render(模板字符串,数据data)
可以使用
<script type='text/template'>
存储字符串,代码可以高亮,方便编程**
1.定义一个扫描类,scanUtil遇到{{停止(返回指针到{{之前的字符串),scan遇到{{跳过,继续走
// 扫描器类
export default class Scanner{
constructor(templateStr){
this.templateStr = templateStr
// 定义一个指针
this.pos = 0
// 尾巴,定义一个截取返回的字符串,剩下的字符串,一开始是所有的字符串
this.tail = templateStr
}
// 跳过标记的字符串,将尾巴更新到{{后面的字符串
scan(tag){
if(this.tail.indexOf(tag)==0){
this.pos +=tag.length
this.tail = this.templateStr.substring(this.pos)
}
}
// 遇到指定的字符串就停止,然后返回之前到停止的字符串
scanUtil(stoTag){
// 记录上一次停止的位置
const pos_backUp = this.pos
while(this.tail.indexOf(stoTag)!=0&&!this.eos()){
this.pos++
this.tail = this.templateStr.substring(this.pos)
}
return this.templateStr.substring(pos_backUp,this.pos)
}
// end of string 判断指针是否到头了
eos() {
return this.pos >= this.templateStr.length
}
}
2.将截取的字符串转换为tokens,遇到#,提取#作为第一个元素,第二个去掉#号,标记['#',去掉#取剩下的]
import Scanner from "./Scanner.js"
// 将截取的字符串转换为tokens
export default function parseTemplateTokens(templateStr) {
let tokens = []
// 实例一个扫描器
var scanner = new Scanner(templateStr)
var words
while (!scanner.eos()) {
words = scanner.scanUtil('{{')
scanner.scan('{{')
if (!words == '') {
tokens.push(['text', words])
}
words = scanner.scanUtil('}}')
scanner.scan('}}')
if (!words == '') {
// 遇到#,提取#作为第一个元素,第二个去掉#号
if (words[0] == '#') {
tokens.push(['#', words.substring(1)])
} else if (words[0] == '/') {
tokens.push(['/', words.substring(1)])
}else{
tokens.push(['name', words])
}
}
}
return tokens
}
3.将其处理为多维数组,遇到#就给当前数组第二个元素生成一个[], 出栈就判断是不是还有栈,有就追加到当前数组第二个元素,没有就追加到最外一个数组,结束多维数组
// 将tokens处理成嵌套的tokens
export default function (tokens) {
// 最终结果
let nestedTokens = []
// 栈结构,存放小tokens。栈顶的tokens数组中当前操作的这个tokens的第三项小数组
let sections = []
/**
* 收集器,默认指向nestedTkens结果数组
* 当匹配到 # 的时候,改变收集器的指向,指向到当前token中新开辟的下一个维度的token
* 当匹配到 / 的时候,改变收集器的指向,判断当前栈sections是否有值?有值,则指向当前的栈顶;没值,则指向最终结果 nestedTokens
*/
let collector = nestedTokens
for (let i = 0; i < tokens.length; i++) {
let token = tokens[i]
switch (token[0]) {
case '#':
// // 收集当前维度的 子项
collector.push(token)
// 入栈
sections.push(token)
// 修正当前的收集器指向 新开辟的子项为空数组
collector = token[2]= []
console.log(collector, token[2],'collector')
break;
case '/':
// 出栈
sections.pop()
//将其赋值给sections最后一项的第二个元素,如果没有栈就直接给最外面的数组的赋值
collector = sections.length ?
sections[sections.length - 1][2] :
nestedTokens
break;
default:
collector.push(token)
break;
}
}
return nestedTokens
}
4.将tokens和data结合,生成DOM字符串
// 将tokens和data结合,生成DOM字符串
export default function (tokens, data) {
console.log('tokens', tokens, data)
let result = []
//简单的例子追加生成字符串。今天开始学习mustache,我好开心啊
for (let index = 0; index < tokens.length; index++) {
let token =tokens[index]
if (token[0] == 'text') {
result += token[1]
} else if (token[0] == 'name') {
result += data[token[1]]
}
}
console.log(result)
return result
}
5.lookup函数,识别a.b.c和.
/**
* 根据传入的字符串寻找对象中的值
* @param {object} data
* @param {string} keyName
*/
export default function lookup(dataObj, keyName) {
var temp = dataObj
// 传入的keyName可能是a.b.c,也可能是.
if (keyName.indexOf('.') != 0 && keyName !== '.') {
//将其转换为数组
const keys = keyName.split('.')
for (let index = 0; index < keys.length; index++) {
const key = keys[index]
// 有值才往下找
if (temp[key]) {
temp = temp[key]
} else {
throw new Error('寻找的值不存在')
}
}
} else {
temp = temp[keyName]
}
return temp
}
6.处理数组、对象循环,结合renderTemplate实现递归,遍历数组的第二项,然后将里面的每一个元素展示到dom里面
import lookup from "./lookup"
import renderTemplate from "./renderTemplate";
/**
* 处理数组、对象循环,结合renderTemplate实现递归
* @param {array} token
* @param {object} data
*/
export default function (token, data) {
console.log('token处理数组', token)
let resultStr = ''
// data中的实际数据,决定循环的方式
const v = lookup(data, token[1])
for (let i = 0; i < v.length; i++) {
resultStr +=renderTemplate(token[2],{
//展开对象,然后添加属性名为'.': v[i]
...v[i],
'.': v[i]
})
}
return resultStr
}