扫描器 扫描器,主要作用是扫描到{{`之前的数据,`}}
之后的数据,以及它们之间的数据,保存到token里,便于以后拼装
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 class Scanner { constructor (templateStr ){ this .pos = 0 ; this .tail = templateStr; this .templateStr = templateStr; } scan (stopTag ) { if (this .tail .indexOf (stopTag) === 0 ) { this .pos += stopTag.length ; this .tail = this .templateStr .substring (this .pos ); } } scanUtil (stopTag ) { const pos_backup = this .pos ; 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 ( ){ return this .pos >= this .templateStr .length ; } }
将模板转换成token 什么是tokens,也就是将模板转换成如下数组结构:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 ` <ol> {{#students}} <li> 学生{{name}} 好朋友是{{friend.name}} 自己的爱好是 <ol> {{#hobbies}} <li>{{.}}</li> {{/hobbies}} </ol> </li> {{/students}} </ol> `
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 function parseTemplateToTokens (templateStr ) { let tokens = []; let scanner = new Scanner (templateStr) let words; 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折叠 #
和/
之间的token,是一个循环,应该属于上一个token的子项,折叠后的token结构如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 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 ] = []; break case '/' : sections.pop (); collector = sections.length >0 ? sections[sections.length - 1 ][2 ]:nestedTokens; break default : collector.push (token); } } return nestedTokens }
将tokens渲染成dom 思路是将token里面的字符串连接起来,双{}
之间的东西,要用data替换
1 2 3 4 5 6 7 8 9 10 11 12 13 14 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 ]) } else if (token[0 ] === '#' ) { resultStr += parseArray (token, data) } } return resultStr }
处理a.b.c 因为a[b.c]
是不能取到a.b.c
的,数据的,所以要对级联.
取值,做处理:
1 2 3 4 5 6 7 8 9 10 11 12 function lookup (dataObj, keyName) { 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] }
处理建议循环符.
对于.
代表,循环数组中的每一项,当访问父元素的点属性时,可以将数据本身复制给点
1 2 3 4 5 6 7 8 9 10 11 function parseArray (token, data ) { let v = lookup (data, token[1 ]); let resultStr = '' for (let i=0 , len = v.length ;i<len ;++i) { resultStr += renderTemplate (token[2 ], { ...v[i], '.' : v[i] }) } return resultStr }
render函数调用主流程 1 2 3 4 5 6 7 function render (templateStr, data) { let tokens = parseTemplateToTokens (templateStr) console .log (tokens) let result = renderTemplate (tokens,data) console .log (result) return result }
调用 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 <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 ="b.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 >