Vue中Mustache引擎插值語法使用詳解
什么是模板引擎
模板引擎是將數(shù)據(jù)變?yōu)橐晥D最優(yōu)雅的解決方案
以前出現(xiàn)過的其它數(shù)據(jù)變視圖的方法
純 DOM 法
數(shù)組 join 法
在 js 里單雙引號(hào)內(nèi)的內(nèi)容是不能換行的,為了提高 dom 結(jié)構(gòu)可讀性,利用了數(shù)組的 join 方法(將數(shù)組變?yōu)樽址?,注?join 的參數(shù) ‘’ 不可以省略,否則得到的 str 字符串會(huì)是以逗號(hào)間隔的
es6 的模板字符串(``)
剛開始用模板引擎時(shí)可以引用 如下:
<script src="https://cdn.bootcdn.net/ajax/libs/mustache.js/4.2.0/mustache.js"></script> <script src="https://cdn.bootcdn.net/ajax/libs/mustache.js/4.2.0/mustache.js"></script>
底層核心機(jī)理
//最簡單的模板的實(shí)現(xiàn)機(jī)理,利用的是正則表達(dá)式中的replace()方法
// replace()的第二個(gè)參數(shù)可以是一個(gè)函數(shù),這個(gè)函數(shù)提供捕獲的東西的參數(shù),就是$1
//結(jié)合data的對(duì)象,即可進(jìn)行智能的替換

//編譯普通對(duì)象成token
const templateStr = `<h3>我今天買了一部{{thing}}手機(jī),花了我{{money}}元,心情好{{mood}}啊</h3>`;
[
["text",'<h3>我今天買了一部'],
["name",'thing'],
["text",'手機(jī),花了我'],
["name",'money']
["text",'元,心情好'],
["name","mood"],
["text",'啊']
]模塊化打包工具有webpack(webpack-dev-server) 、rollup、Parcel等
我們經(jīng)常使用webpack進(jìn)行模塊化打包
實(shí)現(xiàn) Scanner 類
Scanner 類的實(shí)例就是一個(gè)掃描器,用來掃描構(gòu)造時(shí)作為參數(shù)提供的那個(gè)模板字符串
- 屬性
- pos:指針,用于記錄當(dāng)前掃描到字符串的位置
- tail:尾巴,值為當(dāng)前指針之后的字符串(包括指針當(dāng)前指向的那個(gè)字符)
- 方法
- scan:無返回值,讓指針跳過傳入的結(jié)束標(biāo)識(shí) stopTag
- scanUntil:傳入一個(gè)指定內(nèi)容 stopTag 作為讓指針 pos 結(jié)束掃描的標(biāo)識(shí),并返回掃描內(nèi)容
// Scanner.js
export default class Scanner {
constructor(templateStr) {
this.templateStr = templateStr
// 指針
this.pos = 0
// 尾巴
this.tail = templateStr
}
scan(stopTag) {
this.pos += stopTag.length // 指針跳過 stopTag,比如 stopTag 是 {{,則 pos 就會(huì)加2
this.tail = this.templateStr.substring(this.pos) // substring 不傳第二個(gè)參數(shù)直接截取到末尾
}
scanUntil(stopTag) {
const pos_backup = this.pos // 記錄本次掃描開始時(shí)的指針位置
// 當(dāng)指針還沒掃到最后面,并且當(dāng)尾巴開頭不是 stopTag 時(shí),繼續(xù)移動(dòng)指針進(jìn)行掃描
while (!this.eos() && this.tail.indexOf(stopTag) !== 0){
this.pos++ // 移動(dòng)指針
this.tail = this.templateStr.substring(this.pos) // 更新尾巴
}
return this.templateStr.substring(pos_backup, this.pos) // 返回掃描過的字符串,不包括 this.pos 處
}
// 指針是否已經(jīng)抵達(dá)字符串末端,返回布爾值 eos(end of string)
eos() {
return this.pos >= this.templateStr.length
}
}
根據(jù)模板字符串生成 tokens
tokens是一個(gè)JS的嵌套數(shù)組,說白了,就是模板字符串的JS表示
有了 Scanner 類后,就可以著手去根據(jù)傳入的模板字符串生成一個(gè) tokens 數(shù)組了。最終想要生成的 tokens 里的每一條 token 數(shù)組的第一項(xiàng)用 name(數(shù)據(jù)) 或 text(非數(shù)據(jù)文本) 或 #(循環(huán)開始) 或 /(循環(huán)結(jié)束) 作為標(biāo)識(shí)符
// parseTemplateToTokens.js
import Scanner from './Scanner.js'
import nestTokens from './nestTokens' // 后面會(huì)解釋
// 函數(shù) parseTemplateToTokens
export default templateStr => {
const tokens = []
const scanner = new Scanner(templateStr)
let word
while (!scanner.eos()) {
word = scanner.scanUntil('{{')
word && tokens.push(['text', word]) // 保證 word 有值再往 tokens 里添加
scanner.scan('{{')
word = scanner.scanUntil('}}')
/**
* 判斷從 {{ 和 }} 之間收集到的 word 的開頭是不是特殊字符 # 或 /,
* 如果是則這個(gè) token 的第一個(gè)元素相應(yīng)的為 # 或 /, 否則為 name
*/
word && (word[0] === '#' ? tokens.push(['#', word.substr(1)]) :
word[0] === '/' ? tokens.push(['/', word]) : tokens.push(['name', word]))
scanner.scan('}}')
}
return nestTokens(tokens) // 返回折疊后的 tokens, 詳見下文
}
在 index.js 引入 parseTemplateToTokens
// index.js
import parseTemplateToTokens from './parseTemplateToTokens.js'
window.My_TemplateEngine = {
render(templateStr, data) {
const tokens = parseTemplateToTokens(templateStr)
console.log(tokens)
}
}
這樣我們就可以把傳入的 templateStr 初步轉(zhuǎn)成 tokens 了,比如 templateStr 為
const templateStr = `
<ul>
{{#arr}}
<li>
<div>{{name}}的基本信息</div>
<div>
<p>{{name}}</p>
<p>{{age}}</p>
<div>
<p>愛好:</p>
<ol>
{{#hobbies}}
<li>{{.}}</li>
{{/hobbies}}
</ol>
</div>
</div>
</li>
{{/arr}}
</ul>
`
那么目前經(jīng)過 parseTemplateToTokens 處理將得到如下的 tokens

實(shí)現(xiàn) tokens 的嵌套
新建 nestTokens.js 文件,定義 nestTokens 函數(shù)來做 tokens 的嵌套功能,將傳入的 tokens 處理成包含嵌套的 nestTokens 數(shù)組返回。
然后在 parseTemplateToTokens.js 引入 nestTokens,在最后 return nestTokens(tokens)。
在 nestTokens 中,我們遍歷傳入的 tokens 的每一個(gè) token,遇到第一項(xiàng)是 # 和 /的分別做處理,其余的做一個(gè)默認(rèn)處理。
大致思路是當(dāng)遍歷到的 token 的第一項(xiàng)為 # 時(shí),就把直至遇到配套的 / 之前,遍歷到的每一個(gè) token 都放入一個(gè)容器(collector)中,把這個(gè)容器放入當(dāng)前 token 里作為第 3 項(xiàng)元素
但這里有個(gè)問題:在遇到匹配的 / 之前又遇到 # 了怎么辦?也就是如何解決循環(huán)里面嵌套循環(huán)的情況?
解決方法就是新建一個(gè) 棧數(shù)據(jù)類型 的數(shù)組(stack),遇到一個(gè) #,就把當(dāng)前 token 放入這個(gè)棧中,讓 collector 指向這個(gè) token 的第三個(gè)元素。
遇到下一個(gè) # 就把新的 token 放入棧中,collector 指向新的 token 的第三個(gè)元素。
遇到 / 就把棧頂?shù)?token 移出棧,collector 指向移出完后的棧頂 token。
這就利用了棧的先進(jìn)后出的特點(diǎn),保證了遍歷的每個(gè) token 都能放在正確的地方,也就是 collector 都能指向正確的地址。
// nestTokens.js
export default (tokens) => {
const nestTokens = []
const stack = []
let collector = nestTokens // 一開始讓收集器 collector 指向最終返回的數(shù)組 nestTokens
tokens.forEach(token => {
switch (token[0]) {
case '#':
stack.push(token)
collector.push(token)
collector = token[2] = [] // 連等賦值
break
case '/':
stack.pop(token)
collector = stack.length > 0 ? stack[stack.length-1][2] : nestTokens
break;
default:
collector.push(token)
break
}
})
return nestTokens
}
One More Thing
上面的代碼中有用到 collector = token[2] = [],是為連等賦值,相當(dāng)于
token[2] = [] collector = token[2]
看著簡單,其實(shí)暗含著小坑,除非你真的了解它,否則盡量不要使用。比如我在別處看到這么一個(gè)例子,
let a = {n:1};
a.x = a = {n:2};
console.log(a.x); // 輸出?
答案是 undefined,你做對(duì)了嗎?

tokens 結(jié)合數(shù)據(jù)解析為 dom 字符串
大致思路是遍歷 tokens 數(shù)組,根據(jù)每條 token 的第一項(xiàng)的值來做不同的處理,為 text 就直接把 token[1]
加入到最終輸出的 dom 字符串,為 name 則根據(jù) token[1] 去 data 里獲取數(shù)據(jù),結(jié)合進(jìn)來。
當(dāng) data 里存在多層嵌套的數(shù)據(jù)結(jié)構(gòu),比如 data = { test: { a: { b: 10 } } },這時(shí)如果某個(gè) token 為 [“name”, “test.a.b”],即代表數(shù)據(jù)的 token 的第 2 項(xiàng)元素是 test.a.b 這樣的有多個(gè)點(diǎn)符號(hào)的值,那么我么直接通過 data[test.a.b] 是無法拿到正確的值的,因?yàn)?js 不認(rèn)識(shí)這種寫法。
我們需要提前準(zhǔn)備一個(gè) lookup 函數(shù),用以正確獲取數(shù)據(jù)。
定義 lookup 函數(shù)
// lookup.js
// 思路就是先獲取 test.a 的值, 比如說是 temp, 再獲取 temp.b 的值, 一步步獲取
export default (data, key) => {
// 如果傳入的 key 里有點(diǎn)符號(hào)而且不是僅僅只是點(diǎn)符號(hào)
if (key.indexOf('.') !== -1 && key !== '.' ) {
const keys = key.split('.') // 將 key 用 . 分割成一個(gè)數(shù)組
return keys.reduce((acc, cur) => {
return acc[cur] // 一步步獲取
}, data)
}
// 如果傳入的 key 沒有點(diǎn)符號(hào),直接返回
return data[key]
}
定義 renderTemplate 函數(shù)
接下來寫個(gè) renderTemplate 函數(shù)將 tokens 和 data 作為參數(shù)傳入,解析為 dom 字符串了。
// renderTemplate.js
import lookup from './lookup.js'
import parseArray from './parseArray.js'
export default (tokens, data) => {
let domString = ''
tokens.forEach(token => {
switch (token[0]) {
case 'text':
domString += token[1]
break
case 'name':
domString += lookup(data, token[1])
break
case '#':
domString += parseArray(token[2], data[token[1]])
break
default:
break
}
})
return domString
}
需要注意的是遇到循環(huán)的情況,也就是當(dāng)某個(gè) token 的第一項(xiàng)為 “#” 時(shí),要再次遞歸調(diào)用 renderTemplate 函數(shù)。這里我們新定義了一個(gè) parseArray 函數(shù)來處理。
// parseArray.js
import renderTemplate from './renderTemplate.js'
export default (tokens, data) => {
let domString = ''
data.forEach(itemData => {
domString += renderTemplate(tokens, {
...itemData,
'.': itemData // 針對(duì)簡單數(shù)組的情況,即模板字符串里的 {{.}}
})
})
return domString
}
到此這篇關(guān)于Vue中Mustache引擎插值語法使用詳解的文章就介紹到這了,更多相關(guān)Vue Mustache內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
VSCode Vue開發(fā)推薦插件和VSCode快捷鍵(小結(jié))
這篇文章主要介紹了VSCode Vue開發(fā)推薦插件和VSCode快捷鍵(小結(jié)),文中通過圖文表格介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-08-08
vue 動(dòng)態(tài)添加/刪除dom元素節(jié)點(diǎn)的操作代碼
這篇文章主要介紹了vue 動(dòng)態(tài)添加/刪除dom元素,需要在點(diǎn)擊添加時(shí),增加一行key/value的輸入框;點(diǎn)擊垃圾桶圖標(biāo)時(shí),刪除對(duì)應(yīng)行,本文結(jié)合實(shí)例代碼給大家講解的非常詳細(xì),需要的朋友可以參考下2022-12-12
vue 導(dǎo)航錨點(diǎn)_點(diǎn)擊平滑滾動(dòng),導(dǎo)航欄對(duì)應(yīng)變化詳解
這篇文章主要介紹了vue 導(dǎo)航錨點(diǎn)_點(diǎn)擊平滑滾動(dòng),導(dǎo)航欄對(duì)應(yīng)變化詳解,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2020-08-08
Vue3 響應(yīng)式 API 及 reactive 和 ref&
響應(yīng)式是一種允許以聲明式的方式去適應(yīng)變化的編程范例,這篇文章主要介紹了關(guān)于Vue3響應(yīng)式API及reactive和ref的用法,需要的朋友可以參考下2023-06-06
cesium開發(fā)之如何在vue項(xiàng)目中使用cesium,使用離線地圖資源
這篇文章主要介紹了cesium開發(fā)之如何在vue項(xiàng)目中使用cesium,使用離線地圖資源問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-04-04
Vue?計(jì)算屬性之姓名案例的三種實(shí)現(xiàn)方法
這篇文章主要介紹了Vue?計(jì)算屬性之姓名案例的三種實(shí)現(xiàn)方法,計(jì)算屬性實(shí)現(xiàn)、methods實(shí)現(xiàn)和插值語法實(shí)現(xiàn),下面文章具體介紹,需要的小伙伴可以參考一下2022-05-05
vue3+ts數(shù)組去重方及reactive/ref響應(yīng)式顯示流程分析
這篇文章主要介紹了vue3+ts數(shù)組去重方法-reactive/ref響應(yīng)式顯示,本文通過示例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-04-04

