Vue源码学习笔记-mustache模板引擎(八)-解决对象的循环嵌套

423 阅读4分钟
// renderTp.js 嵌套循环# Parse2Array 与 RenderTp 递归调用
import LookUp from './lookUp'
import Parse2Array from './parse2Array'
/**
 * 让tokens变为dom字符串
 */
export default function (tokens, data) {
  // 结果字符串
  let convertedStr = ''
  tokens.forEach(token => { 
    // 看类型
    if (token[0] === 'text') { // 文本直接将内容直接 拼接
      convertedStr += token[1]
    } else if (token[0] === 'name') {
      // 如果是 name 类型,则要使用lookup 防止 a.b.c有点符号号行是
      convertedStr += LookUp(token[1], data)
    } else if (token[0] === '#') {
       // 增加#的对象/数组 嵌套模式
       // token 和 数据
      convertedStr += Parse2Array(token, data)
    }
  })
  return convertedStr
}
// parse2Array.js 解决“.”的问题 层级嵌套
// 嵌套循环# RenderTp与 Parse2Array 递归调用
import LookUp from './lookUp'
import RenderTp  from './renderTp'
/**
 * 处理数组,结合rederTps互相调用实现递归
 * 递归的次数,取决于数据中数组内容的次数
 * token,一个简单的模板['#','student', []] 内部tokens
 * { name: "高乐", hobbies: ["抽烟", "喝酒"] },
 * { name: "于谦", hobbies: ["抽烟", "喝酒", "烫头"] },
 * { name: "GDG", hobbies: ["抽烟", "喝酒", "剃头"] },
 * 那么parseArray()函数就要递归调用renderTp函数 data数组的次数
 */
export default function (token, data) {
  // console.log(token)
  // console.log(data)
  // 结果数组 
  let result = ''
  // 得到数据数组中要使用的部分
  const val = LookUp(token[1], data)
  // val是 # 数据里数组
  val.forEach(v => {
    console.log(token[2], v)
    /**
     *  “.”的tokens无法解析 需要单独处理
     * result += RenderTp(token[2], v)
     * 单独处理的方式,单独增加“.”则无法处理对象中的其他属性,
     * 所以需要 拓展运算符,展开原内容,再原内容基础上增加“.”对应内容
     * */
    result += RenderTp(token[2], {
      ...v,
      '.' : v
    })
  })
  
  return result
}
// lookUp方法未更新
/**
 * 功能是可以在dataObj对象中,寻找连续点符号的keyName属性
 * 比如,dataObj是
 * {
 *    a:{
 *      b:{
 *        c: 100
 *      }
 *    }
 * }
 * 那么lookup(dataObj,'a.b.c)
 */
export default function (keyName, dataObj) {
  // 查看keyname中是否有".",符号 但不能是"."本身
  if (keyName.indexOf(".") != -1 && keyName != '.' ) {
    let keys = keyName.split(".");
    // 设置临时变量
    let temp = dataObj;
    let i = 0;
    while (i < keys.length) {
      // 每找一层都要重新存储
      temp = temp[keys[i]];
      i++;
    }
    return temp;
  }
  // 没有点
  return dataObj[keyName]
}

// 数据和模板
<div id="container"></div>
const tmpStr = `<div>
                    <ol>
                      {{#students}}
                        <li class="student">
                          学生[{{cname}}]的爱好<ol>
                          {{#hobbies}}
                            <li>
                              {{.}}
                            </li>
                          {{/hobbies}}
                          </ol>
                        </li>
                      {{/students}}
                    </ol>
                </div>`;
      const data = {
        students: [
          { cname: "高乐", hobbies: ["抽烟", "喝酒"] },
          { cname: "于谦", hobbies: ["抽烟", "喝酒", "烫头"] },
          { cname: "GDG", hobbies: ["抽烟", "剃头"] },
        ],
      };
      const domStr = TpEngine.render(tmpStr, data);
      container.innerHTML = domStr;
// Scanner.js 扫描类未调整
// 创建扫描类
export default class Scanner {
  constructor(tpStr) {
    console.log('render函数里 调用Scanner')
    this.tpStr = tpStr
    // 建立指针
    this.pos = 0
    // 尾部字符串,默认为整个字符串
    this.tail = tpStr
  }
  scan(tag) {
    if (this.tail.indexOf(tag) === 0) {
      // tag 有多长,指针就往后移动几位
      this.pos += tag.length
      // tail1也要调整
      this.tail = this.tpStr.substring(this.pos)
    }
  }
  // 让指针扫描吗,直到内容结束,并返回之前查到的文字
  scanUtil(stopTag) {
    // 记录指针原始位置
    const pos_backup = this.pos
    // 当尾巴的开头不是 stopTag的时候,就说明没有扫描到stopTag
    // 写 && 很有必要,防止越界 
    while (this.tail.indexOf(stopTag) !== 0 && !this.eos()) {
      this.pos++
      //
      // console.log('pos--------------', this.pos)
      // 重新计算尾巴,从指针开始,到最后一位(第一个参数是启始,第二个是个数,如果省略了该参数)
      this.tail = this.tpStr.substr(this.pos)
    }
    // 返回位于 String 对象中指定位置的子字符串(两个指定下标之间的字符,如果省略该参数,那么返回的子串会一直到字符串的结尾)
    return this.tpStr.substring(pos_backup, this.pos)
  }
  // 指针防止越界
  eos() {
    return this.pos >= this.tpStr.length
  }
}
// parseTp2Tokens.js 未调整
import Scanner from './scaner'
import NextTokens from './nextTokens'
import RenderTp  from './renderTp'
export default function (tpStr, data) {
  const tokens = []
  // 创建扫描器
  const ScannerInit = new Scanner(tpStr)
  let words;
  // 扫描器
  while (!ScannerInit.eos()) {
    // 收集开始出现前的文字
    words = ScannerInit.scanUtil('{{')
    if (words !== '') {
      // 尝试写一下去掉空格,智能判断是普通文字的空格,还是标签中的空格
      // 标签中的空格不能去掉,比如 <div class="box"><></div> 不能去掉class前面的空格
      let isInJJH = false
      // 空白字符串
      var _words = ''
      words.split('').forEach(word => {
        // 判断是否在标签里
        if (word === '<') {
          isInJJH = true
        } else if (word === '>') {
          isInJJH = false
        }
        if (!/\s/.test(word)) {
          _words += word
        } else {
          // 如果这项是空格,只有当它在标签内的时候,才拼接上
          if (isInJJH) {
            _words += word
          }
        }
          
      })
      tokens.push(['text', _words])
    }
    // 跳过大括号
    ScannerInit.scan('{{')
    words = ScannerInit.scanUtil('}}')
    // 收集标记之间的
    if (words !== '') {
      // 存储
      if (words[0] === '#') {
        // 从下标为1 跳过#号开始存
        tokens.push(['#', words.substring(1)])
      } else if (words[0] === '/') {
        tokens.push(['/', words.substring(1)])
      } else {
        tokens.push(['name', words])
      }
    }
    // 跳过大括号
    ScannerInit.scan('}}')
  }
  // 整理tokens
  const nextTokens = NextTokens(tokens)
  // 折叠token
  return RenderTp(nextTokens, data)

}
// nextTokens.js 折叠tokens未改变
/**
 * 折叠tokens
 * 栈底数组第一位,栈顶-数组尾
 */
export default function (tokens) {
  // console.log(tokens)
  // 结果数组
  let nestedTokens = []
  // 栈数字组,存放小token,栈顶(靠近端口的,最新进入的)tokens数组中当前操作的这个tokens小数组
  let sections = []
  // 收集器,初始指向结果数组,引用类型值,所以指向同一个数组
  // 收集器指向会变化,当遇见#的时候,收集器会指向这个token的下表为2的新数组
  let collector = nestedTokens
  for (let i = 0; i < tokens.length; i++) {
    let token = tokens[i]
    switch (token[0]) {
      case '#':
        // console.log(token, '------------')
        // 收集器里放入token,由于是引用类型,所以同时可以push到nestedTokens最终结果中
        collector.push(token)
        // 入栈
        sections.push(token)
        // console.log('collector',  collector,' nestedTokens', nestedTokens, '-----#---0----')
        // 收集器更换, 给token添加下标为2的数组,并更换收集器指向,改变了collector的引用指向,切断了collector,与最终结果的关系
        // 下面等同于 token[2] = []; collector = token[2]
        collector = token[2] = []
        //  console.log('collector',  collector,' nestedTokens', nestedTokens, '-----#----1---')
        // console.log(nestedTokens)
        break;
      case '/':
        // console.log(sections, '-------------')
        // 出栈,pop()会返回弹出的项目
        sections.pop()
        // 改变收集器为栈结构队尾(队尾为栈顶),那项的下标为2的数组
        collector = sections.length > 0 ? sections[sections.length -1][2] : nestedTokens
        break;
      default:
       //  console.log('collector',  collector,' nestedTokens', nestedTokens, '-----default-------')
        // 如果 没进入栈,则直接结果里存,如果如栈了 就要往栈里 也就是 栈的 【2】数组里存
        collector.push(token)
    }
  }
 return nestedTokens
}