Vue-mustache模板引擎


mustache模板引擎

數據變為視圖的方法

純 DOM 法 - 非常笨拙,沒有實戰價值

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>01_數據變為視圖-純DOM法</title>
</head>

<body>
  <ul id="list"></ul>
  
  <script>
    var arr = [
      { name: '小明', age: 12, sex: '男' },
      { name: '小紅', age: 11, sex: '女' },
      { name: '小強', age: 13, sex: '男' },
    ]
    var list = document.getElementById('list')
    for (let i = 0; i < arr.length; i++) {
      // 每遍歷一項,都要用 DOM 方法去創建 li 標簽
      let oLi = document.createElement('li')
      // 創建 hd 這個 div
      let hdDiv = document.createElement('div')
      hdDiv.className = 'hd'
      hdDiv.innerText = arr[i].name + '的基本信息'
      oLi.appendChild(hdDiv)
      // 創建 bd 這個 div
      let bdDiv = document.createElement('div')
      bdDiv.className = 'bd'
      // bdDiv.innerText = arr[i].name + '的基本信息'
      // 創建 3 個 p
      let p1 = document.createElement('p')
      p1.innerText = '姓名:' + arr[i].name
      bdDiv.appendChild(p1)
      let p2 = document.createElement('p')
      p2.innerText = '年齡:' + arr[i].age
      bdDiv.appendChild(p2)
      let p3 = document.createElement('p')
      p3.innerText = '性別:' + arr[i].sex
      bdDiv.appendChild(p3)
      oLi.appendChild(bdDiv)
      // 創建的節點是孤兒節點,所以必須要上樹才能讓用戶看見
      list.appendChild(oLi)
    }
  </script>
</body>
</html>

數組 join 法 - 曾幾何時非常流行,是曾經的前端必備知識

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>02_數據變為視圖-數組join法</title>
</head>

<body>
  <ul id="list"></ul>
    
  <script>
    var arr = [
      { name: '小明', age: 12, sex: '男' },
      { name: '小紅', age: 11, sex: '女' },
      { name: '小強', age: 13, sex: '男' },
    ]
    var list = document.getElementById('list')
    // 遍歷 arr 數組,每遍歷一項,就以字符串的視角將HTML字符串添加到list中
    for (let i = 0; i < arr.length; i++) {
      list.innerHTML += [
        '<li>',
        '  <div class="hd">' + arr[i].name + '的信息</div>',
        '  <div class="bd">',
        '    <p>姓名:' + arr[i].name + '</p>',
        '    <p>年齡:' + arr[i].age + '</p>',
        '    <p>性別:' + arr[i].sex + '</p>',
        '  </div>',
        '</li>'
      ].join('')
    }
  </script>
</body>
</html>

ES6 的反引號法 - ES6 中新增 ${a}語法糖,很實用

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>03_數據變為視圖-ES6反引號法</title>
</head>

<body>
  <ul id="list"></ul>
  
  <script>
    var arr = [
      { name: '小明', age: 12, sex: '男' },
      { name: '小紅', age: 11, sex: '女' },
      { name: '小強', age: 13, sex: '男' },
    ]
    var list = document.getElementById('list')
    // 遍歷 arr 數組,每遍歷一項,就以字符串的視角將HTML字符串添加到list中
    for (let i = 0; i < arr.length; i++) {
      list.innerHTML += `
        <li>
          <div class="hd">${arr[i].name}的基本信息</div>
          <div class="bd">
            <p>姓名:${arr[i].name}</p>
            <p>年齡:${arr[i].age}</p>
            <p>性別:${arr[i].sex}</p>
          </div>
        </li>
      `
    }
  </script>
</body>
</html>

mustache 的基本使用

mustache 庫簡介

  • mustache的官方githttps://github.com/janl/mustache.js
  • mustache 是 “胡子” 的意思,因為它的嵌入標記 {{ }}非常像胡子
  • {{ }}的語法也被 Vue 沿用
  • mustache 是最早的模板引擎庫,比 Vue 誕生的早多了,它的底層實現機理在當時是非常有創造性的、轟動性的,為后續模板引擎的發展提供了嶄新的思路

mustache 的基本使用

  • 必須引入 mustache 庫,可以在 https://www.bootcdn.cn/ 上找到它
  • mustache 的模板語法非常簡單,比如前述案例的模板語法如下:
{{#arr}}
  <li>
    <div class="hd">{{name}}的基本信息</div>
    <div class="bd">
      <p>姓名:{{name}}</p>
      <p>年齡:{{age}}</p>
      <p>性別:{{sex}}</p>
    </div>
  </li>
{{/arr}}
  • 循環對象數組
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>04_數據變為視圖-mustache模板引擎</title>
  <script src="https://cdn.bootcdn.net/ajax/libs/mustache.js/4.1.0/mustache.js"></script>
</head>

<body>
  <div id="container"></div>
  
  <script>
    var templateStr = `
      <ul id="list">
        {{#arr}}
        <li>
          <div class="hd">{{name}}的基本信息</div>
          <div class="bd">
            <p>姓名:{{name}}</p>
            <p>年齡:{{age}}</p>
            <p>性別:{{sex}}</p>
          </div>
        </li>
        {{/arr}}
      </ul>
    `
    var data = {
      arr: [
        { name: '小明', age: 12, sex: '男' },
        { name: '小紅', age: 11, sex: '女' },
        { name: '小強', age: 13, sex: '男' },
      ]
    }
    var domStr = Mustache.render(templateStr, data)
    var container = document.getElementById('container')
    container.innerHTML = domStr
  </script>
</body>
</html>
  • 不循環
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>05_數據變為視圖-mustache模板引擎-不循環</title>
  <script src="https://cdn.bootcdn.net/ajax/libs/mustache.js/4.1.0/mustache.js"></script>
</head>

<body>
  <div id="container"></div>
    
  <script>
    var templateStr = `
      <h1>我買了一個{{thing}},好{{mood}}啊</h1>
    `
    var data = {
      thing: '華為手機',
      mood: '開心'
    }
    var domStr = Mustache.render(templateStr, data)
    var container = document.getElementById('container')
    container.innerHTML = domStr
  </script>
</body>
</html>
  • 循環簡單數組
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>06_數據變為視圖-mustache模板引擎-循環簡單數組</title>
  <script src="https://cdn.bootcdn.net/ajax/libs/mustache.js/4.1.0/mustache.js"></script>
</head>

<body>
  <div id="container"></div>

  <script>
    var templateStr = `
      <ul>
        {{#arr}}
          <li>{{.}}</li>  // 遍歷當前數組
        {{/arr}}  
      </ul>
    `
    var data = {
      arr: ['蘋果', '梨子', '香蕉']
    }
    var domStr = Mustache.render(templateStr, data)
    var container = document.getElementById('container')
    container.innerHTML = domStr
  </script>
</body>
</html>
  • 數組的嵌套情況
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>07_數據變為視圖-mustache模板引擎-數組的嵌套情況</title>
  <script src="https://cdn.bootcdn.net/ajax/libs/mustache.js/4.1.0/mustache.js"></script>
</head>

<body>
  <div id="container"></div>

  <script>
    var templateStr = `
      <ul>
        {{#arr}}
          <li>{{name}}的愛好是:
              <ol>
                {{#hobbies}}
                  <li>{{.}}</li>
                {{/hobbies}}
              </ol>
          </li>
        {{/arr}}  
      </ul>
    `
    var data = {
      arr: [
        { name: '小明', age: 12, hobbies: ['游泳', '羽毛球'] },
        { name: '小紅', age: 11, hobbies: ['編程', '寫作文', '看報紙'] },
        { name: '小強', age: 13, hobbies: ['打台球'] }
      ]
    }
    var domStr = Mustache.render(templateStr, data)
    var container = document.getElementById('container')
    container.innerHTML = domStr
  </script>
</body>
</html>
  • 布爾值
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>08_數據變為視圖-mustache模板引擎-布爾值</title>
  <script src="./mustache.js"></script>
</head>

<body>
  <div id="container"></div>

  <script>
    var templateStr = `
    {{#m}}
      <h1>哈哈哈</h1>
    {{/m}}
    `
    var data = {
      m: true
    }
    var domStr = Mustache.render(templateStr, data)
    var container = document.getElementById('container')
    container.innerHTML = domStr
  </script>
</body>
</html>
  • script模板寫法
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>09_數據變為視圖-mustache模板引擎-模板寫法</title>
  <script src="./mustache.js"></script>
</head>

<body>
  <div id="container"></div>

  <!-- 模板 -->
  <script type="text/template" id="mytemplate">
    <ul id="list">
        {{#arr}}
        <li>
          <div class="hd">{{name}}的基本信息</div>
          <div class="bd">
            <p>姓名:{{name}}</p>
            <p>年齡:{{age}}</p>
            <p>性別:{{sex}}</p>
          </div>
        </li>
        {{/arr}}
      </ul>
  </script>

  <script>
    var templateStr = document.getElementById('mytemplate').innerHTML
    var data = {
      arr: [
        { name: '小明', age: 12, sex: '男' },
        { name: '小紅', age: 11, sex: '女' },
        { name: '小強', age: 13, sex: '男' },
      ]
    }
    var domStr = Mustache.render(templateStr, data)
    var container = document.getElementById('container')
    container.innerHTML = domStr
  </script>
</body>
</html>

mustache 的底層核心機理

replace() 方法實現簡單地模板數據填充

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>10_正則表達式實現簡單的模板數據填充</title>
</head>

<body>
  <div id="container"></div>

  <script>
    var templateStr = '<h1>我買了一個{{thing}},好{{mood}}</h1>'
    var data = {
      thing: '華為手機',
      mood: '開心'
    }
    // 最簡單的模板引擎實現機理,利用的是正則表達式中的 replace() 方法
    // replace() 的第二個參數可以是一個函數,這個函數提供捕獲的東西的參數,就是 $1
    // 結合data對象,即可進行智能的替換
    function render(templateStr, data) {
      return templateStr.replace(/\{\{(\w+)\}\}/g, function (findStr, $1) {
        return data[$1]
      })
    }
    var domStr = render(templateStr, data)
    var container = document.getElementById('container')
    container.innerHTML = domStr
  </script>
</body>
</html>

mustache 庫的機理

mustache 庫的機理

什么是 tokens?

  • tokens就是JS的嵌套數組,說白了,就是模板字符串的JS表示
  • 它是“抽象語法書”、“虛擬節點”等等的開山鼻祖
  • 模板字符串
<h1>我買了一個{{thing}},好{{mood}}啊</h1>
  • tokens
[
  ["text", "<h1>我買了一個"],
  ["name", "thing"],
  ["text", "好"],
  ["name", "mood"],
  ["text", "啊</h1>"]
]

循環情況下的 tokens

  • 當模板字符串中有循環存在時,它將被編譯為嵌套更深的 tokens
  • 模板字符串
<div>
  <ul>
    {{#arr}}
    <li>{{.}}</li>
    {{/arr}}
  </ul>
</div>
  • tokens
[
  ["text", "<div><ul>"],
  [
    "#",
    "arr", 
    [
      ["text", "li"],
      ["name", "."],
      ["text": "</li>"]
    ]
  ],
  ["text", "</ul></div>"]
]

雙重循環下的 tokens

  • 當循環是雙重的,那么 tokens會更深一層
  • 模板字符串
<div>
  <ol>
    {{#students}}
    <li>
      學生{{name}}的愛好是
      <ol>
        {{#hobbies}}
        <li>{{.}}</li>
        {{/hobbies}}
      </ol>
    </li>
    {{/students}}
  </ol>
</div>
  • tokens
[
  ["text", "<div><ol>"],
  ["#", "students",
    [
      ["text", "<li>學生"],
      ["name", "name"],
      ["text", "的愛好是<ol>"],
      ["#", "hobbies",
        [
          ["text", "<li>"],
          ["name", "."],
          ["text", "</li>"]
        ]
      ],
      ["text", "</ol></li>"]
    ]
  ],
  ["text", "</ol></div>"]
]

mustache 庫底層重點要做2個事情

  1. 將模板字符串編譯為 tokens 形式
  2. 將 tokens 結合數據,解析為 dom 字符串

手寫實現 mustache 庫

使用 webpack 和 webpack-dev-server 構建

  • mustache 官方庫使用 rollup 模塊化打包
  • 生成庫是 UMD 的,這意味着它同時在 nodejs 環境中使用,也可以在瀏覽器環境使用。實現 UMD 不難,只需要一個 “通用頭” 即可
  • 使用 webpack 和 webpack-dev-server 構建
    • 新建目錄 Template-Engine
    • cd Template-Engine
    • npm init -y
    • npm i -D webpack@5 webpack-cli@3 webpack-dev-server@3
    • 新建 webpack.config.js 文件
    • 將如下配置拷貝到 webpack.config.js 中
const path = require('path')

module.exports = {
  // 入口
  entry: './src/index.js',
  // 出口
  output: {
    // 虛擬打包路徑,就是說文件夾不會真正生成,而是在 8080 端口虛擬生成,不會真正的物理生成
    publicPath: 'xuni',
    // 打包出來的文件名
    filename: 'bundle.js'
  },
  devServer: {
    // 端口號
    port: 8080,
    // 靜態資源文件夾
    contentBase: 'www'
  }
}
  • 新建 src/index.js 文件 --- 測試
alert('hello')
  • 新建 www/index.html 文件
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>
  <h1>Hello</h1>
  
  <script src="xuni/bundle.js"></script>
</body>
</html>
  • package.json 文件中新增命令:
{
  "scripts": {
    "dev": "webpack-dev-server",
  }
}
  • 終端運行 npm run dev
  • 訪問:http://localhost:8080/http://127.0.0.1:8080/xuni/bundle.js, 可以看到 www/index.htmlxuni/bundle.js 文件的內容

實現 Scanner 掃描器類

  • www/index.html
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>
  <h1>Hello</h1>
  
  <script src="xuni/bundle.js"></script>
  <script>
    var templateStr = '<h1>我買了一個{{thing}},好{{mood}}啊</h1>'
    var data = {
      thing: '華為手機',
      mood: '開心'
    }
    TemplateEngine.render(templateStr, data)
  </script>
</body>
</html>
  • src/index.js
import Scanner from './Scanner'

window.TemplateEngine = {
  render(templateStr, data) {
    console.log('render函數被調用,命令Scanner工作')
    // 實例化一個掃描器 構造時候提供一個參數,這個參數就是模板字符串
    // 也就是說這個掃描器就是針對這個模板字符串工作的
    var scanner = new Scanner(templateStr)
    while (scanner.pos !== templateStr.length) {
      var words = scanner.scanUtil('{{')
      console.log(words)
      scanner.scan('{{')
      words = scanner.scanUtil('}}')
      console.log(words)
      scanner.scan('}}')
    }
  }
}
  • src/Scanner.js
/*
掃描器類
*/
export default class Scanner {
  constructor(templateStr) {
    // console.log('我是Scanner', 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(stopTap) {
    // 記錄一下執行本方法的時候pos的值
    const pos_backup = this.pos
    // 當尾巴的開頭不是 stopTag的時候,就說明沒有掃描到stopTag
    // 寫 && 很有必要,防止越界
    while (this.tail.indexOf(stopTap) !== 0 && !this.eos()) {
      this.pos++
      // 改變尾巴為從當前指針這個字符開始,到最后的全部字符
      this.tail = this.templateStr.substr(this.pos)
    }
    return this.templateStr.substring(pos_backup, this.pos)
  }

  // 指針是否已經到頭,返回布爾值,end of string
  eos() {
    return this.pos >= this.templateStr.length
  }
}

生成 tokens 數組

  • www/index.html 把測試數據換掉
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>
  <h1>Hello</h1>
  
  <script src="xuni/bundle.js"></script>
  <script>
    var templateStr = `
      <div>
        <ol>
          {{#students}}
          <li>
            學生{{name}}的愛好是
            <ol>
              {{#hobbies}}
              <li>{{.}}</li>
              {{/hobbies}}
            </ol>
          </li>
          {{/students}}
        </ol>
      </div>
    `
    var data = {
      thing: '華為手機',
      mood: '開心'
    }
    TemplateEngine.render(templateStr, data)
  </script>
</body>
</html>
  • src/index.js實現一個將模板字符串解析成 tokens 的方法 parseTemplateToTokens()
import parseTemplateToTokens from './parseTemplateToTokens'

window.TemplateEngine = {
  // 渲染方法
  render(templateStr) {
    // 調用 parseTemplateToTokens 函數,讓模板字符串能夠變成 tokens 數組
    var tokens = parseTemplateToTokens(templateStr)
    console.log(tokens)
  }
}
  • 新建文件 src/parseTemplateToTokens.js
import Scanner from './Scanner'
import nestTokens from './nestTokens'

/**
 * 將模板字符串變為 tokens 數組
 * @param {string} templateStr
 */
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
  return nestTokens(tokens)
}
  • src/nestTokens.js 嵌套 tokens 的折疊處理
/**
* 函數的功能是折疊 tokens,將#和/之間的tokens能夠整合起來
* 作為它的下標為3的項
* @param {array} tokens
*/
export default function nestTokens(tokens) {
	// 結果數組
    var nestedTokens = []
    // 棧結構,存放小tokens,棧頂(靠近端口的,最新進入的)的tokens數組中當前操作的這個tokens小數組
    var sections = []
    // 收集器,天生指向nestedTokens結果數組,引用類型值,所以指向的是同一個數組
    // 收集器的指向會變化,當遇見#的時候,收集器會指向這個token的下標為2的新數組
    var collector = nestedTokens
    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的項,並且讓收集器指向它
          collector = token[2] = []
          break
        case '/':
          // 出棧 pop()會返回剛剛彈出的項
          sections.pop()
          // 改變收集器為棧結構隊尾(隊尾是棧頂)那項的下標為2的數組
          collector =
            sections.length > 0 ? sections[sections.length - 1][2] : nestedTokens
          break
        default:
          // 甭管當前的collector是誰,可能是結果nestedTokens,也可能是某個token的下標為2的數組,甭管是誰,推入collector即可
          collector.push(token)
          break
      }
    }
    return nestedTokens
}

tokens 結合數據,解析為 dom 字符串

  • # 標記的 tokens,需要遞歸處理它的下標為2的小數組
  • www/index.html 將返回的 domStr 添加到 dom 樹里
  <!DOCTYPE html>
  <html lang="en">

  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
  </head>

  <body>
    <div id="container"></div>
    
    <script src="xuni/bundle.js"></script>
    <script>
      var templateStr = `
        <div>
          <ol>
            {{#students}}
            <li>
              學生{{name}}的愛好是
              <ol>
                {{#hobbies}}
                <li class='myli'>{{.}}</li>
                {{/hobbies}}
              </ol>
            </li>
            {{/students}}
          </ol>
        </div>
      `
      var data = {
        students: [
          { name: '小明', hobbies: ['游泳', '健身'] },
          { name: '小紅', hobbies: ['足球', '籃球', '羽毛球'] },
          { name: '小強', hobbies: ['吃飯', '睡覺'] },
        ]
      }
      var domStr = TemplateEngine.render(templateStr, data)
      var container = document.getElementById('container')
      container.innerHTML = domStr
    </script>
  </body>
  </html>
  • src/index.js 此時,render() 函數返回生成的 domStr
import parseTemplateToTokens from './parseTemplateToTokens'
import renderTemplate from './renderTemplate'

window.SGG_TemplateEngine = {
  // 渲染方法
  render(templateStr, data) {
    // 調用 parseTemplateToTokens 函數,讓模板字符串能夠變成 tokens 數組
    var tokens = parseTemplateToTokens(templateStr)
    // 調用 renderTemplate 函數,讓 tokens數組變為 dom 字符串
    var domStr = renderTemplate(tokens, data)
    return domStr
  }
}
  • src/renderTemplate.jstokens 數組變為 dom 字符串
import lookup from './lookup'
import parseArray from './parseArray'

/**
 * 讓 tokens數組變為 dom 字符串
 * @param {array} tokens
 * @param {object} data
 */
export default function renderTemplate(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] === '#') {
      resultStr += parseArray(token, data)
    }
  }
  return resultStr
}
  • src/parseArray.js 解析數組及嵌套數組
import lookup from './lookup'
import renderTemplate from './renderTemplate'

/**
 * 處理數組,結合 renderTemplate 實現遞歸
 * 這個函數接受的參數是token 而不是 tokens
 * token 是什么,就是一個簡單的 ['#', 'students', []]
 *
 * 這個函數要遞歸調用 renderTemplate 函數
 * 調用的次數由 data 的深度決定
 */
export default function parseArray(token, data) {
  // 得到整體數據data中這個數組要使用的部分
  var v = lookup(data, token[1])
  // 結果字符串
  var resultStr = ''
  // 遍歷v數組,v一定是數組
  // 遍歷數據
  for (let i = 0; i < v.length; i++) {
    // 這里要補一個 “.” 屬性的識別
    resultStr += renderTemplate(token[2], v[i])
  }
  return resultStr
}
  • src/lookup.js
/**
 * 功能是可以在 dataObj 對象中,用連續點符號的 keyName 屬性
 * 比如,dataObj是
 * {
 *    a: {
 *      b: {
 *        c: 100
 *      }
 *    }
 * }
 * 那么 lookup(dataObj, 'a.b.c') 結果就是 100
 * @param {object} dataObj
 * @param {string} keyName
 */
export default function lookup(dataObj, keyName) {
  /*
  // 看看 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]
  */
  // 這里其實可以不用加是否包含 . 符號的判斷 因為 'abc'.split('.') = ["abc"]
  // 只有一個元素不影響最終結果,不影響循環語句最終結果
  // 另外,這里的特征是:當前的值要依賴前一個的值,所以可以用 reduce 累加器
  // 一行代碼搞定
  return keyName !== '.'
    ? keyName
        .split('.')
        .reduce((prevValue, currentKey) => prevValue[currentKey], dataObj)
    : dataObj
}
  • src/parseTemplateToTokens.js 添加智能處理空格的邏輯
import Scanner from './Scanner'
import nestTokens from './nestTokens'
/**
 * 將模板字符串變為 tokens 數組
 * @param {string} templateStr
 */
export default function parseTemplateToTokens(templateStr) {
  var tokens = []
  // 實例化一個掃描器 構造時候提供一個參數,這個參數就是模板字符串
  var scanner = new Scanner(templateStr)
  var words
  // 讓掃描器工作
  while (!scanner.eos()) {
    // 收集開始標記之前的文字
    words = scanner.scanUtil('{{')
    // 存起來
    if (words !== '') {
      // 嘗試寫一下去掉空格,智能判斷是普通文字的空格,還是標簽中的空格
      // 標簽中的空格不能去掉,比如 <div class="box"><></div> 不能去掉class前面的空格
      let isInJJH = false
      // 空白字符串
      var _words = ''
      for (let i = 0; i < words.length; i++) {
        // 判斷是否在標簽里
        if (words[i] === '<') {
          isInJJH = true
        } else if (words[i] === '>') {
          isInJJH = false
        }
        if (!/\s/.test(words[i])) {
          _words += words[i]
        } else {
          // 如果這項是空格,只有當它在標簽內的時候,才拼接上
          if (isInJJH) {
            _words += words[i]
          }
        }
      }
      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
  return nestTokens(tokens)
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM