扫描器 扫描器,主要作用是扫描到{{`之前的数据,`}}之后的数据,以及它们之间的数据,保存到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 >