目录
数据data又怎们和tokens结合变为变为理想的dom然后上树的?
parseTemplateToTokens(将HTML变为tokens)
renderTemplate`(将tokens和data数据结合,返回处理好的dom然后上树)
两个几个问题
HTML模板如何变为tokens形式的嵌套格式的数组?
数据data又怎们和tokens结合变为变为理想的dom
然后上树的?
流程
-
www
文件夹下的index为入口文件,全局封装对象Fel_TemplateEngine
(自定义) -
内部暴露一个render函数作为入口,供页面调用接收两个参数,模板(
templateStr
)和数据(data) -
Scanner类,作为扫描器将模板编译为tokens(无嵌套格式,一个数组走到末尾)
-
nextTokens
类,编译好的tokens做处理,当做 -
建立
parseTemplateToTokens
类,
parseTemplateToTokens
(将HTML变为tokens)
-
理解:
parseTemplateToTokens
将Scanner扫描器和nextTokens
(零星的tokens嵌套起来) -
scanner在的作用是扫描模板字符串,遇到"{{" 、"}}"、"#"、"/"、"^"等符号进行拆分,并整合到一个数组中,但是结果是一个常规的数组,并未分层,和嵌套数组所应该有的嵌套结构不符,所以就有了
nextTokens
()方法,将这个无嵌套的数组转为原本模板对应的嵌套结构 -
import Scanner from './Scanner.js' import nextTokens from './nextTokens.js' /** * 将模板字符串变为数组 */ export default function parseTemplateToTokens(templateStr){ var tokens = []; // 创建扫描器 var scanner = new Scanner(templateStr); var words; //让扫描器工作 while(!scanner.eos()){ // 收集开始标记 words = scanner.scanUtil("{{"); // 保存 if(words!=''){ tokens.push(['text',words]); } // 过双大括号 scanner.scan("{{") // 收集开始标记 words = scanner.scanUtil("}}"); // 保存 if(words!=''){ // 这时的words就是{{}}中间的东西,判断首字符 if(words[0] == "#"){ // 存起来,从下标为1的项开始存,因为下标为0的像是# tokens.push(["#",words.substring(1)]); }else if(words[0] == '/'){ // 存起来,从下标为1的项开始存,因为下标为0的项是/ tokens.push(["/",words.substring(1)]); }else{ // 存起来 tokens.push(['name',words]); } } // 过双大括号 scanner.scan("}}") } // 返回折叠收集的tokens console.log(tokens); return nextTokens(tokens); }
Scanner扫描器
-
单独建立
Scanner.js
文件,作为扫描器类 -
内部三个函数
-
constructor构造函数接收一个模板(
templateStr
) -
scan扫描方法
-
scanUtil
方法
-
-
大致流程
以此模板为例
我买了一个{{thing}},好{{mod}}阿
左侧起始位置一个指针,指针往右移动从"我"到"个"
的这个过程为
scanUtil ,然后以scan扫描"{{",指针做过"thing"的过程为
scanUtil
....直至末尾 -
相关知识点
-
substring()方法
1、定义和用法 substring() 方法用于提取字符串中介于两个指定下标之间的字符。 substring() 方法返回的子串包括 开始 处的字符,但不包括 结束 处的字符。 2、语法 string.substring(from, to) 3、参数 from 必需。一个非负的整数,规定要提取的子串的第一个字符在 string Object 中的位置。 to 可选。一个非负的整数,比要提取的子串的最后一个字符在 string Object 中的位置多 1。 如果省略该参数,那么返回的子串会一直到字符串的结尾。
-
1、定义和用法 indexOf() 方法可返回某个指定的字符串值在字符串中首次出现的位置。 如果没有找到匹配的字符串则返回 -1。 注意: indexOf() 方法区分大小写。 提示: 同样你可以查看类似方法 lastIndexOf() 。 2、语法 string.indexOf(searchvalue,start) 3、参数 searchvalue 必需。规定需检索的字符串值。 start 可选的整数参数。规定在字符串中开始检索的位置。它的合法取值是 0 到 string Object.length - 1。如省略该参数,则将从字符串的首字符开始检索。 4、返回值 Number类型 查找指定字符串第一次出现的位置,如果没找到匹配的字符串则返回 -1。
-
-
Scanner.js
代码/** * 扫描类 */ export default class Scanner{ constructor(templateStr){ console.log(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){ // 记录一下执行本方法的时候的pos的值 const pos_backup = this.pos; // 当尾巴的开头不是stopTag的时候,说明还没有扫描到stopTag // 写&&很必要,因为防止找不到,那么寻找到最后也要停下来 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; } }
nextTokens
(处理tokens为嵌套结构)
-
利用栈的先入后出的思想,结合对应的储存从而实现结构的更改,比较精妙的还有收集器的创建,一直指向最终要返回的数组,类似于一种引用,看似一直操作的是这个收集器,其实也相当于对要返回的数组进行添加,通过收集器指向的改变控制返回数组的结构。
-
/** * 函数的功能是折叠tokens,将#和/之间的tokens能够整合起来,最为他的下标为3的项 */ export default function nextTokens(tokens){ // 结果数组 var nextedTokens = []; // 栈结构,存放小的tokens,栈顶(靠近端口的,最新进入的)的tokens数组中当前的操作的这个tokens小数组 var sections = []; // 收集器,天生指向nextedTokens结果数组,引用类型值,所以指向的是同一个数组 // 收集器的指向会变化,当遇到#的时候,收集器会指向这个下标为2的新数组 var collector = nextedTokens; 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的项,并且让收集器指向他 token[2]=[]; collector=token[2]; break; case '/': // 出栈,pop()会返回刚刚弹出的项 sections.pop(); // 改变收集器为栈结构队尾(队尾为栈顶) 那项的下标为2的数组 collector = sections.length>0?sections[sections.length-1][2]:nextedTokens; break; default: collector.push(token); } } return nextedTokens; }
renderTemplate`(将tokens和data
数据结合,返回处理好的dom
然后上树)
-
内部通过对tokens的循环,判断当前token的第一个值如果text则直接加在需要返回的数据后面,如果是name则说明要添加data数据,不过在添加之前需要用lookup函数处理,防止识别错误造成渲染失败,遇到#号则说明需要循环数组遍历添加数据,结合
parseArray
函数来回嵌套形成递归调用从而将处理数组及其嵌套的数据渲染 -
import lookup from "./lookup.js"; import parseArray from "./parseArray.js"; /** * 函数的功能是让tokens数组变为dom字符串 */ export default function renderTemplate(tokens,data){ console.log(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'){ // 如果是name类型,直接使用他的值,使用lookup // 因为防止出现'a.b.c'的形式造成的无法识别问题 resultStr += lookup(data, token[1]); }else if(token[0] == '#'){ console.log(token); resultStr += parseArray(token,data) } } return resultStr; }
lookup(寻找用连续点符号的keyName
属性)
-
/** * 功能是可以在dataObj对象中,寻找用连续点符号的keyName属性 * 比如dataObj是 * { * a:{ * b:{ * c:100 * } * } * } * 那么lookup(dataObj,'a.b.c')结果是abc */ export default function lookup(dataObj, 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
(循环数组渲染dom
)
-
import lookup from "./lookup.js" import renderTemplate from "./renderTemplate.js" /** * 处理数组,结合renderTemplate实现递归 * 注意函数接收的参数是token不是tokens * * 这个函数要递归调用renderTemplate函数,调用次数由data界定 */ export default function parseArray(token,data){ // 得到整体数组中这个数据要使用的部分 console.log(token,data); var v= lookup(data,token[1]); console.log(v); // 结果字符串 var resultStr = ''; // 遍历v数组,v可能为数组也可能为布尔值,此处布尔值忽略 // 这个循环可能是这个包最那思考的一个循环 // 他是遍历数据而不是遍历tokens for(let i=0; i< v.length; i++) { resultStr += renderTemplate(token[2],{ // 现在这个数据小对象是v[i]的展开,就是v[i]本身 ...v[i], '.':v[i] }) } console.log(resultStr); return resultStr }
梳理
-
mustache
中的模板引擎的功能主要是将HTML模板代码和需要渲染的数据相结合,然后是返回模板和数据结合后的DOM,模板引擎在工作中可以分为两大块,一个是将事先准备好的HTML解析为tokens数组的形式,方便后面和数据之间的结合(parseTemplateToTokens
),另一个是就是将data和tokens结合返回真实的dom了(renderTemplate
)。 -
在
parseTemplateToTokens
中它又可以分为两小块,一个是扫描器(scanner.js,Scanner类)一个是nextTokens
方法,将初步得到的tokens处理为嵌套的,和原DOM结构嵌套而是一样的形式 -
在
renderTemplate
中也有又两小块,一个是lookup判断处理防止渲染出错,另一个是parseArray
方法循环数组渲染dom
,利用栈的思想将tokens读取完毕即结束,返回最终dom
总结
-
尽管是跟着视频把代码敲了出来但是还需要进一步的研究,其中的一些奥妙还是需要继续理解一番,对一些函数的处理理解的并不是很透彻,总结不是很到位,包括这个记录还是又不少问题,比如自身的理解太少、比如内容相对有点拖泥带水感觉,再接再励了。