**
模板字符串(编译)=> tokens (转换)=> DOM字符串
**
index.js
index.js文件就是两个步骤,第一步调用parseTemplateToTokens函数将模板字符串转换成tokens数组,第二步调用renderTemplate将tokens数组转换成DOM字符串返回。主要还是parseTemplateToTokens.js和renderTemplate.js文件
import parseTemplateToTokens from './parseTemplateToTokens.js'
import renderTemplate from './renderTemplate'
window.Template = {
render(templateStr, data) {//templateStr为模板字符串,data为模板字符串对应的数据。
//parseTemplateToTokens函数,让模板字符串能够变为tokens数组
var tokens = parseTemplateToTokens(templateStr)
//调用renderTemplateToTokens函数,让token数组变成为DOM字符串
return renderTemplate(tokens, data)//将DOM字符串返回
}
}
parseTemplateToTokens.js
该文件的作用就是将板字符串变为tokens数组,该文件引入了Scanner.js文件和nestTokens.js文件。可以先看Scanner.js文件的作用,再来看parseTemplateToTokens.js文件。nestTokens.js文件是将命名为#和/中间的数组全添加到#数组的下标为2的数组中,nestTokens.js文件是关键也是难点。
import Scanner from './Scanner.js'
import nestTokens from './nestTokens.js'
export default function parseTemplateToTokens(templateStr) {
var tokens = []
//创建扫描器
var scanner = new Scanner(templateStr)
var words
//扫描器工作
while (!scanner.eos()) {
//收集开始标记出现之前的文字
words = scanner.scanUtil('{{')
//存到tokens数组中,这里说明words不是在"{{"和"}}"中间的值,将words字符串命名为text
tokens.push(['text', words])
//跳过双大括号
scanner.scan('{{')
//收集开始标记出现之前的文字
words = scanner.scanUtil('}}')
if (words != '') {//这里说明words是在"{{"和"}}"中间的值,如果开头是#可能是循环也可能是判断,我们这里只考虑循环的情况,如果是/说明循环结束。如果不是#和/说明words是data中的一个属性值。
//这个words就是{{}}中间的东西,判断一下首字符串
if (words[0] == '#') {
tokens.push(['#', words.substring(1)])
} else if (words[0] == '/') {
tokens.push(['/', words.substring(1)])
} else {
//存到tokens数组中
tokens.push(['name', words])
}
}
//跳过双大括号
scanner.scan('}}')
}
//返回折叠的tokens
return nestTokens(tokens)
}
Scanner.js
Scanner.js是一个扫描器
文件的作用:一个{{name}}的模板字符串,当遇到"{{"或"}}"字符串后就执行scan函数,跳过模板字符串,如果不是,就返回"{{"和"}}"中间的字符串。
//扫描器类
export default class{
constructor(templateStr){
//让this.templateStr是传过来的模板字符串
this.templateStr=templateStr
//指针
this.pos=0
//尾巴,一开始就是模板字符串的原文
this.tail=templateStr
}
//功能弱,就是走过指定内容,没有返回值
scan(tag){
if(this.tail.indexOf(tag)==0){
//tag有多长,比如{{长度是2,就跳过两个长度
this.pos+=tag.length
this.tail=this.templateStr.substring(this.pos)
}
}
//让指针进行扫描,知道遇见指定内容结束,并且能否返回结束之前路过的文字
scanUtil(stopTag){//stopTag是Scanner.js文件传过来的值,是"{{"或"}}"
//记录一下pos(开始值)
const pos_backup=this.pos
//当尾巴的开头不是stopTag的时候,就说明还没扫描到stopTag
/*
this.tail.indexOf(stopTag)!=0判断tail的第一位不是"{{"或"}}"
如果不是则进入while,让pos++,再重新赋值给this.tail,
直到this.tail.indexOf(stopTag)!=0判断不成立,也就是this.tail的第一位是
"{{"或"}}"this.templateStr.substring(pos_backup,this.pos),也就是从开始位置pos_backup到结束位置this.pos截取this.templateStr返回。
然后返回
*/
while(!this.eos()&&this.tail.indexOf(stopTag)!=0){
this.pos++
//改变尾巴为从当前指针这个字符开始,到最后的全部字符
this.tail=this.templateStr.substring(this.pos)
}
return this.templateStr.substring(pos_backup,this.pos)
}
//指针是否已经到头,返回布尔值
eos(){
return this.pos>=this.templateStr.length
}
}
nestTokens.js
其中最关键的就是collector和nestedTokens的指向问题,这里最开始nestedTokens和collector指向一个数组,当遇到#后,nestedTokens在下标为2的地方创建一个空数组,这时候collector指向空数组,sections收集之后的内容,知道遇到/后再将sections收集的内容的最后一个数组出栈,只是后改变collector的指向,指向sections的最后一个元素,如果sections的长度为0,再次指向nestedTokens。
/**
* 函数功能是折叠tokens,将#和/之间的tokens能够整合起来,作为它的下标为3的项
*/
export default function nestTokens(tokens) {
//nestedTokens结果数组,最终返回的数组
var nestedTokens = []
//栈结构,存放小tokens,栈顶(靠近端口的,最新进入的)的tokens数组中当前 *** 作的这个tokens小数组
var sections = []
//收集器,天生指向nestedTokens结果数组,引用类型值,所以指向的是同一个数组
//收集器的指向会变化,当遇到#时收集器会指向这个token的小标为2的新数组
var collector = nestedTokens
for (let i = 0; i < tokens.length; i++) {
let token = tokens[i]
switch (token[0]) {
case '#':
//收集器中放入token
collector.push(token)
//入栈
sections.push(token)
//收集器换人,给token添加下标为2的项,并且让收集器指向它
collector = token[2] = []/*这一行代码十分关键,
上面写了collector.push(token),改变token,
collector刚push的值也会跟着改变
此时token[2]还指向collector,所以改变collector,
collector刚push的值也会跟着改变
*/
break
case '/':
//出栈,pop()会返回刚刚弹出的项
// let section_pop = sections.pop()
sections.pop()
//改变收集器为栈结构队尾(队尾是栈顶)那项的下标为2的数组
collector = sections.length > 0 ? sections[sections.length - 1] : nestedTokens
break
default:
collector.push(token)
}
}
return nestedTokens
}
tokens转换成DOM字符串
renderTemplate.js
renderTemplate.js引入了lookup.js如果name是a.b的形式,也可以获取对象中的a参数的b参数的值,引入parseArray.js,是让#里面的子元素数组也转换成DOM字符串。
/**
* 函数的功能是让tokens数组变成dom字符串
*/
import lookup from './lookup.js'
import parseArray from './parseArray.js'
export default function renderTemplate(tokens, data) {
//结果字符串
var resultStr = ''
//遍历tokens
for (let i = 0; i < tokens.length; i++) {
let token = tokens[i]
//判断类型
if (token[0] == 'text') {
resultStr += token[1]
} else if (token[0] == 'name') {
resultStr += lookup(data, token[1])
} else if (token[0] == '#') {
resultStr += parseArray(token, data)
}
}
return resultStr
}
lookup.js
/**
* 功能是可以在dataObj对象中,寻找用连续点符号的keyName属性
* 这个一般会有面试题
*/
export default function lookup(dataObj, keyName) {
//看看keyname中有没有.符号,但是不能是点本身
if (keyName.indexOf('.') != -1 && keyName != '.') {
var keys = keyName.split('.')
var temp = dataObj
for (let i = 0; i < keys.length; i++) {
temp = temp[keys[i]]
}
return temp
}
return dataObj[keyName]
}
parseArray.js
/**
* 处理数组,结合renderTemplate实现递归
*/
import lookup from './lookup.js'
import renderTemplate from './renderTemplate.js'
export default function parseArray(token, data) {
//得到整体数据data中这个数组要使用的部分
var v = lookup(data, token[1])
//结果字符串
var resultStr = ''
//遍历v数组,
for (let i = 0; i < v.length; i++) {
//这里要补充一个'.'的使用
resultStr += renderTemplate(token[2], {
...v[i],
'.': v[i]
})
}
return resultStr
}
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)