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

什么是 tokens?
- tokens就是JS的嵌套數組,說白了,就是模板字符串的JS表示
- 它是“抽象語法書”、“虛擬節點”等等的開山鼻祖
- 模板字符串
<h1>我買了一個{{thing}},好{{mood}}啊</h1>
[
["text", "<h1>我買了一個"],
["name", "thing"],
["text", "好"],
["name", "mood"],
["text", "啊</h1>"]
]
循環情況下的 tokens
- 當模板字符串中有循環存在時,它將被編譯為嵌套更深的 tokens
- 模板字符串
<div>
<ul>
{{#arr}}
<li>{{.}}</li>
{{/arr}}
</ul>
</div>
[
["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>
[
["text", "<div><ol>"],
["#", "students",
[
["text", "<li>學生"],
["name", "name"],
["text", "的愛好是<ol>"],
["#", "hobbies",
[
["text", "<li>"],
["name", "."],
["text", "</li>"]
]
],
["text", "</ol></li>"]
]
],
["text", "</ol></div>"]
]
mustache 庫底層重點要做2個事情
- 將模板字符串編譯為 tokens 形式
- 將 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')
<!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>
{
"scripts": {
"dev": "webpack-dev-server",
}
}
- 終端運行
npm run dev
- 訪問:
http://localhost:8080/ 和 http://127.0.0.1:8080/xuni/bundle.js, 可以看到 www/index.html 和 xuni/bundle.js 文件的內容
實現 Scanner 掃描器類
<!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>
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('}}')
}
}
}
/*
掃描器類
*/
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 數組
<!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.js 讓 tokens 數組變為 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
}
/**
* 功能是可以在 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)
}