Vue中Mustache引擎插值語法使用詳解
什麼是模板引擎
模板引擎是將數據變為視圖最優雅的解決方案
以前出現過的其它數據變視圖的方法
純 DOM 法
數組 join 法
在 js 裡單雙引號內的內容是不能換行的,為瞭提高 dom 結構可讀性,利用瞭數組的 join 方法(將數組變為字符串),註意 join 的參數 ‘’ 不可以省略,否則得到的 str 字符串會是以逗號間隔的
es6 的模板字符串(“)
剛開始用模板引擎時可以引用 如下:
<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>
底層核心機理
//最簡單的模板的實現機理,利用的是正則表達式中的replace()方法
// replace()的第二個參數可以是一個函數,這個函數提供捕獲的東西的參數,就是$1
//結合data的對象,即可進行智能的替換
//編譯普通對象成token const templateStr = `<h3>我今天買瞭一部{{thing}}手機,花瞭我{{money}}元,心情好{{mood}}啊</h3>`; [ ["text",'<h3>我今天買瞭一部'], ["name",'thing'], ["text",'手機,花瞭我'], ["name",'money'] ["text",'元,心情好'], ["name","mood"], ["text",'啊'] ]
模塊化打包工具有webpack(webpack-dev-server) 、rollup、Parcel等
我們經常使用webpack進行模塊化打包
實現 Scanner 類
Scanner 類的實例就是一個掃描器,用來掃描構造時作為參數提供的那個模板字符串
– 屬性
- pos:指針,用於記錄當前掃描到字符串的位置
- tail:尾巴,值為當前指針之後的字符串(包括指針當前指向的那個字符)
– 方法
- scan:無返回值,讓指針跳過傳入的結束標識 stopTag
- scanUntil:傳入一個指定內容 stopTag 作為讓指針 pos 結束掃描的標識,並返回掃描內容
// 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 就會加2 this.tail = this.templateStr.substring(this.pos) // substring 不傳第二個參數直接截取到末尾 } scanUntil(stopTag) { const pos_backup = this.pos // 記錄本次掃描開始時的指針位置 // 當指針還沒掃到最後面,並且當尾巴開頭不是 stopTag 時,繼續移動指針進行掃描 while (!this.eos() && this.tail.indexOf(stopTag) !== 0){ this.pos++ // 移動指針 this.tail = this.templateStr.substring(this.pos) // 更新尾巴 } return this.templateStr.substring(pos_backup, this.pos) // 返回掃描過的字符串,不包括 this.pos 處 } // 指針是否已經抵達字符串末端,返回佈爾值 eos(end of string) eos() { return this.pos >= this.templateStr.length } }
根據模板字符串生成 tokens
tokens是一個JS的嵌套數組,說白瞭,就是模板字符串的JS表示
有瞭 Scanner 類後,就可以著手去根據傳入的模板字符串生成一個 tokens 數組瞭。最終想要生成的 tokens 裡的每一條 token 數組的第一項用 name(數據) 或 text(非數據文本) 或 #(循環開始) 或 /(循環結束) 作為標識符
// parseTemplateToTokens.js import Scanner from './Scanner.js' import nestTokens from './nestTokens' // 後面會解釋 // 函數 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 的開頭是不是特殊字符 # 或 /, * 如果是則這個 token 的第一個元素相應的為 # 或 /, 否則為 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 初步轉成 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> `
那麼目前經過 parseTemplateToTokens 處理將得到如下的 tokens
實現 tokens 的嵌套
新建 nestTokens.js 文件,定義 nestTokens 函數來做 tokens 的嵌套功能,將傳入的 tokens 處理成包含嵌套的 nestTokens 數組返回。
然後在 parseTemplateToTokens.js 引入 nestTokens,在最後 return nestTokens(tokens)
。
在 nestTokens 中,我們遍歷傳入的 tokens 的每一個 token,遇到第一項是 # 和 /的分別做處理,其餘的做一個默認處理。
大致思路是當遍歷到的 token 的第一項為 # 時,就把直至遇到配套的 / 之前,遍歷到的每一個 token 都放入一個容器(collector)中,把這個容器放入當前 token 裡作為第 3 項元素
但這裡有個問題:在遇到匹配的 / 之前又遇到 # 瞭怎麼辦?也就是如何解決循環裡面嵌套循環的情況?
解決方法就是新建一個 棧數據類型 的數組(stack),遇到一個 #,就把當前 token 放入這個棧中,讓 collector 指向這個 token 的第三個元素。
遇到下一個 # 就把新的 token 放入棧中,collector 指向新的 token 的第三個元素。
遇到 / 就把棧頂的 token 移出棧,collector 指向移出完後的棧頂 token。
這就利用瞭棧的先進後出的特點,保證瞭遍歷的每個 token 都能放在正確的地方,也就是 collector 都能指向正確的地址。
// nestTokens.js export default (tokens) => { const nestTokens = [] const stack = [] let collector = nestTokens // 一開始讓收集器 collector 指向最終返回的數組 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] = []
,是為連等賦值,相當於
token[2] = [] collector = token[2]
看著簡單,其實暗含著小坑,除非你真的瞭解它,否則盡量不要使用。比如我在別處看到這麼一個例子,
let a = {n:1}; a.x = a = {n:2}; console.log(a.x); // 輸出?
答案是 undefined,你做對瞭嗎?
tokens 結合數據解析為 dom 字符串
大致思路是遍歷 tokens 數組,根據每條 token 的第一項的值來做不同的處理,為 text 就直接把 token[1]
加入到最終輸出的 dom 字符串,為 name 則根據 token[1] 去 data 裡獲取數據,結合進來。
當 data 裡存在多層嵌套的數據結構,比如 data = { test: { a: { b: 10 } } }
,這時如果某個 token 為 [“name”, “test.a.b”],即代表數據的 token 的第 2 項元素是 test.a.b 這樣的有多個點符號的值,那麼我麼直接通過 data[test.a.b] 是無法拿到正確的值的,因為 js 不認識這種寫法。
我們需要提前準備一個 lookup 函數,用以正確獲取數據。
定義 lookup 函數
// lookup.js // 思路就是先獲取 test.a 的值, 比如說是 temp, 再獲取 temp.b 的值, 一步步獲取 export default (data, key) => { // 如果傳入的 key 裡有點符號而且不是僅僅隻是點符號 if (key.indexOf('.') !== -1 && key !== '.' ) { const keys = key.split('.') // 將 key 用 . 分割成一個數組 return keys.reduce((acc, cur) => { return acc[cur] // 一步步獲取 }, data) } // 如果傳入的 key 沒有點符號,直接返回 return data[key] }
定義 renderTemplate 函數
接下來寫個 renderTemplate 函數將 tokens 和 data 作為參數傳入,解析為 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 }
需要註意的是遇到循環的情況,也就是當某個 token 的第一項為 “#” 時,要再次遞歸調用 renderTemplate 函數。這裡我們新定義瞭一個 parseArray 函數來處理。
// parseArray.js import renderTemplate from './renderTemplate.js' export default (tokens, data) => { let domString = '' data.forEach(itemData => { domString += renderTemplate(tokens, { ...itemData, '.': itemData // 針對簡單數組的情況,即模板字符串裡的 {{.}} }) }) return domString }
到此這篇關於Vue中Mustache引擎插值語法使用詳解的文章就介紹到這瞭,更多相關Vue Mustache內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!
推薦閱讀:
- 深入理解PyTorch中的nn.Embedding的使用
- Vue如何防止按鈕重復點擊方案詳解
- JavaScript中分號的一些細節
- Vue實現單點登錄控件的完整代碼
- Vue實現用戶登錄及token驗證