Vue中mustache的探秘

什么是模板引擎,平时使用Vue写循环的时候用v-for那么溜却从来没有想过为什么

Vue中mustache的探秘

mustache基本使用

Vue中mustache的探秘

mustache的底层核心机理

Vue中mustache的探秘

 

那么什么是tokens?

tokens是一个JS的嵌套数组,说白了,就是

mustache库底层重点要做两个事情:

① 将模板字符串编译为 tokens 形式 ② 将 tokens 结合数据,解析为 dom 字符串

板字符串的JS表示

带你手写实现mustache库

将模板字符串变为简单版tokensn

首先可以看到每一行的第一列都有不一样的符号"name","text","#",首先通过ScanUntil函数进行指针pos移动和对应数据字段收集,Scan根据{{,}}等标识符的长度移动pos指针,方便下一次收集

Vue中mustache的探秘

export default class Scaner{
  constructor(template){
    console.log(template)
    this.template = template
    this.pos = 0
    this.tail = template
  }
  scan(tag){
    if(this.tail.indexOf(tag) == 0){//找到了现在要取找到的字符的长度,指针pos移动该长度,方便下一次寻找
      this.pos += tag.length
      this.tail = this.template.substr(this.pos)
    
    }

  }
  sanUtil(stopTag){
    var pos_backup=this.pos
    //0是找到
    while(this.tail.indexOf(stopTag) != 0 && !this.eos()){
      this.pos ++;
      //截取字段为[this.pos,最后]
      this.tail = this.template.substring(this.pos)   
    }
    return this.template.substring(pos_backup,this.pos)
  }
  eos(){
    return this.pos >= this.template.length
  }
}

parseTemplateToToken函数进行将模板字符串进行拆解

Vue中mustache的探秘====>Vue中mustache的探秘

import Scanner from './Scanner'
import nestToken from './nestToken'
export default function parseTemplateToTokens(templateStr){
  var tokens =[] 
  var scanner = new Scanner(templateStr)
  var words 
  while (!scanner.eos()){
      words = scanner.sanUtil('{{')
      if (words !=""){
        var classIn = false
        var _words = '';
        for(var i =0;i<words.length;i++){
          //用正则判断是否有空格
          //没有直接加
          if(words[i]== '<'){
            classIn = true
          }else if(words[i] == '>'){
            classIn = false
          }
          if(!/\s/.test(words[i])){
            _words += words[i]
          }else{
            if(!classIn){
              _words += ''
            }
          }
          
          //有判断是否再< >里面
        }
        tokens.push(['text',_words])
      }

      scanner.scan("{{")

      words = scanner.sanUtil('}}')
      
      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("}}")
  }
  console.log("原数组",tokens)
  return nestToken(tokens)
 
}

 将模板字符串变为复杂版tokens

简单版只是通过{{ ,}}进行拆解但是没有实现嵌套如下图的左图,好比要实现下图右图的#hobbies是在#student数组里面,那么如何实现呢

Vue中mustache的探秘Vue中mustache的探秘

我们的目的是为了数据结构化形成,怎末把hobbies嵌套到student里面呢??思考一下((()))这么多括号是不是互相嵌套呢,我们怎末知道是在那个括号里面?可以通过栈将(依次压入,每次压入指针都在变化,栈顶这时候指向的是第三个左括号,等到遇到右括号),也就说明子括号的结束,然后再改变指针指回上一层(上一级)的左括号,依次类推。那么我们这时候在收集数据,怎末知道那个数据放在哪里,这就很好理解拉,遇到#,就好像遇到左括号,指针指向#,就知道放在那个括号里面拉,如果再遇到新的#指针改变状态再次指向那个新的#,就可以往里面放东西,遇到“/”就知道改变指针退回上一级的#

在js里没有指针这个概念,但是可以想到的是引用类型的浅复制里面,复制了简单的数据结构,但是指向的还是同一个地址,所以可以浅复制出一个数组作为指针。

Vue中mustache的探秘Vue中mustache的探秘

我们通过nestTokens函数实现

/* 
    函数的功能是折叠tokens,将#和/之间的tokens能够整合起来,作为它的下标为3的项
*/
export default function nestTokens(tokens) {
  var nestedToken = []
  var sections = []
  var colltector = nestedToken
 
  for (var i = 0 ;i <tokens.length;i++){
    let token = tokens[i]
    switch(token[0]){
      case '#':
        colltector.push(token)
        sections.push(token)
        //开始收集
        colltector = token[2] = []
        break
      case '/':
        sections.pop()
        colltector = sections.length > 0 ? sections[sections.length - 1][2] : nestedToken;
        break;
      default:
        colltector.push(token)
    }
  }
  console.log("嵌套成",nestedToken)
  return nestedToken
};

将tokens结合数据,解析为dom字符串

首先思考一个问题你拿到tokens和数据,你要渲染几次,当然是根据数据决定拉,因为是把数据往模板上套。

Vue中mustache的探秘Vue中mustache的探秘

这里要注意的是模板里面的key要一样,比如students,hobbies,name,

一开始如果是简单没有数组嵌套的话我们直接通过text对应的文本直接用字符串进行拼接,name也是赋值拼接

这里我们用RenderTemplate函数

import lookup from './lookup.js';
import parseArray from './parseArray';
/* 
    函数的功能是让tokens数组变为dom字符串
*/
export default function renderTemplate(tokens, data) {
    // 结果字符串
    console.log("结果字符串",tokens)
    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);
        }
    }
    console.log(resultStr)
    return resultStr;
}

如果是数组嵌套的话我们用parseArray函数, name对应的key值就有a.b.c,比如student里面还有个出生日期,那么我们再vue中肯定是写birth.year,birth.month,birth.day,但是数据只会给student:[ name:'xixi',birth:{year:1999,month:9,day:9}}

注意了数据没有点,所以为了方便结合,我们在嵌套数组的前面加个键值对

Vue中mustache的探秘Vue中mustache的探秘

 

 

然后我们在lookup函数用高阶函数对a.b.c进行处理,是学习那个数据响应式原理源码的

export default function lookup(dataObj,keyName){
  if (keyName.indexOf('.') != -1 && keyName != '.') {
    var getter = aa(keyName)
    return getter(dataObj)
  }
  return dataObj[keyName]
}
function aa(keyName){
    var segments =keyName.split('.')
      return dataObj=>{
        for(let i = 0,l=segments.length;i<l;i++){
          dataObj = dataObj[segments[i]]
          console.log(dataObj)
        }
        return dataObj
      }
  
}

最后在页面渲染上树页面


    // 渲染上树
    var container = document.getElementById('container');
    container.innerHTML = domStr;

代码

上一篇:RichEditControl自定义高亮语法


下一篇:soul网关学习十一之RateLimitPlugin插件的调用