摘要:是一個弱邏輯的模板引擎,語法十分簡單,使用很方便。源碼只有行,且代碼結構清晰。解析器解析器是整個源碼中最重要的方法,用于解析模板,將標簽與模板標簽分離。同時比較后還需將的最后一個刪除,才能進行下一輪比較。
mustache.js是一個弱邏輯的模板引擎,語法十分簡單,使用很方便。源碼(v2.2.1)只有600+行,且代碼結構清晰。
一般來說,mustache.js使用方法如下:
var template = "Hello, {{name}}"; var rendered = Mustache.render(template, { name: "World" }); document.getElementById("container").innerHTML = rendered;
通過使用Chrome對上述Mustache.render的debug,我們順藤摸瓜梳理了mustache.js5個模塊(暫且稱它們?yōu)椋?b>Utils, Scanner, Parser, Writer,Context)間的關系圖如下:
代碼層面,Mustache.render()方法是mustache.js向外暴露的方法之一,
mustache.render = function render(template, view, partials) { // 容錯處理 if (typeof template !== "string") { throw new TypeError("Invalid template! Template should be a "string" " + "but "" + typeStr(template) + "" was given as the first " + "argument for mustache#render(template, view, partials)"); } // 調用Writer.render return defaultWriter.render(template, view, partials); };
在其內部,它首先調用了Writer.render()方法,
Writer.prototype.render = function render(template, view, partials) { // 調用Writer構造器的parse方法 var tokens = this.parse(template); // 渲染邏輯,后文會分析 var context = (view instanceof Context) ? view : new Context(view); return this.renderTokens(tokens, context, partials, template); };
而Writer.render()方法首先調用了Writer.parse()方法,
Writer.prototype.parse = function parse(template, tags) { var cache = this.cache; var tokens = cache[template]; if (tokens == null) // 調用parseTemplate方法 tokens = cache[template] = parseTemplate(template, tags); return tokens; };
Writer.parse()方法調用了parseTemplate方法,
所以,歸根結底,Mustache.render()方法首先調用parseTemplate方法對html字符串進行解析,
然后,將一個對象渲染到解析出來的模板中去。
所以,我們得研究源碼核心所在——parseTemplate方法。在此之前,我們的先看一些前置方法:工具方法和掃描器。
工具方法(Utils)// 判斷某個值是否為數(shù)組 var objectToString = Object.prototype.toString; var isArray = Array.isArray || function isArrayPolyfill(object) { return objectToString.call(object) === "[object Array]"; }; // 判斷某個值是否為函數(shù) function isFunction(object) { return typeof object === "function"; } // 更精確的返回數(shù)組類型的typeof值為"array",而非默認的"object" function typeStr(obj) { return isArray(obj) ? "array" : typeof obj; } // 轉義正則表達式里的特殊字符 function escapeRegExp(string) { return string.replace(/[-[]{}()*+?.,^$|#s]/g, "$&"); } // 判斷對象是否有某屬性 function hasProperty(obj, propName) { return obj != null && typeof obj === "object" && (propName in obj); } // 正則驗證,防止Linux和Windows下不同spidermonkey版本導致的bug var regExpTest = RegExp.prototype.test; function testRegExp(re, string) { return regExpTest.call(re, string); } // 是否是空格 var nonSpaceRe = /S/; function isWhitespace(string) { return !testRegExp(nonSpaceRe, string); } // 將特殊字符轉為轉義字符 var entityMap = { "&": "&", "<": "<", ">": ">", """: """, """: "'", "/": "/", "`": "`", "=": "=" }; function escapeHtml(string) { return String(string).replace(/[&<>""`=/]/g, function fromEntityMap(s) { return entityMap[s]; }); } var whiteRe = /s*/; // 匹配0個以上的空格 var spaceRe = /s+/; // 匹配1個以上的空格 var equalsRe = /s*=/; // 匹配0個以上的空格加等號 var curlyRe = /s*}/; // 匹配0個以上的空格加} var tagRe = /#|^|/|>|{|&|=|!/; // 匹配#,^,/,>,{,&,=,!掃描器(Scanner)
// Scanner構造器,用于掃描模板 function Scanner(string) { this.string = string; // 模板總字符串 this.tail = string; // 模板剩余待掃描字符串 this.pos = 0; // 掃描索引,即表示當前掃描到第幾個字符串 } // 如果模板掃描完成,返回true Scanner.prototype.eos = function eos() { return this.tail === ""; }; // 掃描的下一批的字符串是否匹配re正則,如果不匹配或者match的index不為0 Scanner.prototype.scan = function scan(re) { var match = this.tail.match(re); if (!match || match.index !== 0) return ""; var string = match[0]; this.tail = this.tail.substring(string.length); this.pos += string.length; return string; }; // 掃描到符合re正則匹配的字符串為止,將匹配之前的字符串返回,掃描索引設為掃描到的位置 Scanner.prototype.scanUntil = function scanUntil(re) { var index = this.tail.search(re), match; switch (index) { case -1: match = this.tail; this.tail = ""; break; case 0: match = ""; break; default: match = this.tail.substring(0, index); this.tail = this.tail.substring(index); } this.pos += match.length; return match; };
總的來說,掃描器,就是用來掃描字符串的。掃描器中只有三個方法:
eos: 判斷當前掃描剩余字符串是否為空,也就是用于判斷是否掃描完了
scan: 僅掃描當前掃描索引的下一堆匹配正則的字符串,同時更新掃描索引
scanUntil: 掃描到匹配正則為止,同時更新掃描索引
現(xiàn)在進入parseTemplate方法。
解析器(Parser)解析器是整個源碼中最重要的方法,用于解析模板,將html標簽與模板標簽分離。
整個解析原理為:遍歷字符串,通過正則以及掃描器,將普通html和模板標簽掃描并且分離,并保存為數(shù)組tokens。
function parseTemplate(template, tags) { if (!template) return []; var sections = []; // 用于臨時保存解析后的模板標簽對象 var tokens = []; // 保存所有解析后的對象 var spaces = []; // 包括空格對象在tokens里的索引 var hasTag = false; // 當前行是否有{{tag}} var nonSpace = false; // 當前行是否有非空格字符 // 去除保存在tokens里的空格對象 function stripSpace() { if (hasTag && !nonSpace) { while (spaces.length) delete tokens[spaces.pop()]; } else { spaces = []; } hasTag = false; nonSpace = false; } var openingTagRe, closingTagRe, closingCurlyRe; // 將tag轉換為正則,默認tag為{{和}},所以轉成匹配{{的正則,和匹配}}的正則,以及匹配}}}的正則 // 因為mustache的解析中如果是{{{}}}里的內容則被解析為html代碼 function compileTags(tagsToCompile) { if (typeof tagsToCompile === "string") tagsToCompile = tagsToCompile.split(spaceRe, 2); if (!isArray(tagsToCompile) || tagsToCompile.length !== 2) throw new Error("Invalid tags: " + tagsToCompile); openingTagRe = new RegExp(escapeRegExp(tagsToCompile[0]) + "s*"); closingTagRe = new RegExp("s*" + escapeRegExp(tagsToCompile[1])); closingCurlyRe = new RegExp("s*" + escapeRegExp("}" + tagsToCompile[1])); } compileTags(tags || mustache.tags); var scanner = new Scanner(template); var start, type, value, chr, token, openSection; while (!scanner.eos()) { start = scanner.pos; // 開始掃描模板,掃描至{{時停止掃描,并且將此前掃描過的字符保存為value value = scanner.scanUntil(openingTagRe); if (value) { // 遍歷{{之前的字符 for (var i = 0, valueLength = value.length; i < valueLength; ++i) { chr = value.charAt(i); // 如果當前字符為空格,這用spaces數(shù)組記錄保存至tokens里的索引 if (isWhitespace(chr)) { spaces.push(tokens.length); } else { nonSpace = true; } tokens.push(["text", chr, start, start + 1]); start += 1; // 如果遇到換行符,則將前一行的空格清除 if (chr === " ") stripSpace(); } } // 判斷下一個字符串中是否有{{,同時更新掃描索引到{{的后一位 if (!scanner.scan(openingTagRe)) break; hasTag = true; // 掃描標簽類型,是{{#}}還是{{=}}或其他 type = scanner.scan(tagRe) || "name"; scanner.scan(whiteRe); // 根據(jù)標簽類型獲取標簽里的值,同時通過掃描器,刷新掃描索引 if (type === "=") { value = scanner.scanUntil(equalsRe); // 使掃描索引更新為s*=后 scanner.scan(equalsRe); // 使掃描索引更新為}}后,下面同理 scanner.scanUntil(closingTagRe); } else if (type === "{") { value = scanner.scanUntil(closingCurlyRe); scanner.scan(curlyRe); scanner.scanUntil(closingTagRe); type = "&"; } else { value = scanner.scanUntil(closingTagRe); } // 匹配模板閉合標簽即}},如果沒有匹配到則拋出異常, // 同時更新掃描索引至}}后一位,至此時即完成了一個模板標簽{{#tag}}的掃描 if (!scanner.scan(closingTagRe)) throw new Error("Unclosed tag at " + scanner.pos); // 將模板標簽也保存至tokens數(shù)組中 token = [type, value, start, scanner.pos]; tokens.push(token); // 如果type為#或者^,也將tokens保存至sections if (type === "#" || type === "^") { sections.push(token); } else if (type === "/") { // 如果type為/則說明當前掃描到的模板標簽為{{/tag}}, // 則判斷是否有{{#tag}}與其對應 openSection = sections.pop(); // 檢查模板標簽是否閉合,{{#}}是否與{{/}}對應,即臨時保存在sections最后的{{#tag}} if (!openSection) throw new Error("Unopened section "" + value + "" at " + start); // 是否跟當前掃描到的{{/tag}}的tagName相同 if (openSection[1] !== value) throw new Error("Unclosed section "" + openSection[1] + "" at " + start); // 具體原理:掃描第一個tag,sections為[{{#tag}}], // 掃描第二個后sections為[{{#tag}}, {{#tag2}}], // 以此類推掃描多個開始tag后,sections為[{{#tag}}, {{#tag2}} ... {{#tag}}] // 所以接下來如果掃描到{{/tag}}則需跟sections的最后一個相對應才能算標簽閉合。 // 同時比較后還需將sections的最后一個刪除,才能進行下一輪比較。 } else if (type === "name" || type === "{" || type === "&") { // 如果標簽類型為name、{或&,不用清空上一行的空格 nonSpace = true; } else if (type === "=") { // 編譯標簽,為下一次循環(huán)做準備 compileTags(value); } } // 確保sections中沒有開始標簽 openSection = sections.pop(); if (openSection) throw new Error("Unclosed section "" + openSection[1] + "" at " + scanner.pos); return nestTokens(squashTokens(tokens)); }
我們來看經(jīng)過解析器解析之后得到的tokens的數(shù)據(jù)結構:
每一個子項都類似下面這種結構
token[0]為token的類型,可能的值有#、^、/、&、name、text,分別表示{}時,調用renderSection方法
Writer.prototype.renderSection = function renderSection(token, context, partials, originalTemplate) { var self = this; var buffer = ""; // 獲取{{#xx}}中xx在傳進來的對象里的值 var value = context.lookup(token[1]); function subRender(template) { return self.render(template, context, partials); } if (!value) return; if (isArray(value)) { // 如果為數(shù)組,說明要復寫html,通過遞歸,獲取數(shù)組里的渲染結果 for (var j = 0, valueLength = value.length; j < valueLength; ++j) { buffer += this.renderTokens(token[4], context.push(value[j]), partials, originalTemplate); } } else if (typeof value === "object" || typeof value === "string" || typeof value === "number") { // 如果value為對象或字符串或數(shù)字,則不用循環(huán),根據(jù)value進入下一次遞歸 buffer += this.renderTokens(token[4], context.push(value), partials, originalTemplate); } else if (isFunction(value)) { if (typeof originalTemplate !== "string") throw new Error("Cannot use higher-order sections without the original template"); // 如果value是方法,則執(zhí)行該方法,并且將返回值保存 value = value.call(context.view, originalTemplate.slice(token[3], token[5]), subRender); if (value != null) buffer += value; } else { // 如果不是上面所有情況,直接進入下次遞歸 buffer += this.renderTokens(token[4], context, partials, originalTemplate); } return buffer; };
當模板標簽類型為時,說明要當value不存在(null、undefined、0、"")或者為空數(shù)組的時候才觸發(fā)渲染。
看看renderInverted方法的實現(xiàn)
Writer.prototype.renderInverted = function renderInverted(token, context, partials, originalTemplate) { var value = context.lookup(token[1]); // 值為null,undefined,0,""或空數(shù)組 // 直接進入下次遞歸 if (!value || (isArray(value) && value.length === 0)) { return this.renderTokens(token[4], context, partials, originalTemplate); } };結語
到這為止,mustache.js的源碼解析完了,可以看出來,mustache.js最主要的是一個解析器和一個渲染器,以非常簡潔的方式實現(xiàn)了一個強大的模板引擎。
文章版權歸作者所有,未經(jīng)允許請勿轉載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/108912.html
摘要:使用方法編譯模板并根據(jù)所給的數(shù)據(jù)立即渲染出結果僅編譯模版暫不渲染,它會返回一個可重用的編譯后的函數(shù)根據(jù)給定的數(shù)據(jù),對之前編譯好的模板進行數(shù)據(jù)渲染參考資料模板引擎概述 js模版引擎介紹 JavaScript 模板是將 HTML 結構從包含它們的內容中分離的方法。模板系統(tǒng)通常會引入一些新語法,但通常是非常簡單的,一個要注意的有趣的點是,替換標記通常是由雙花括號({ {……} })表示,這也...
摘要:現(xiàn)在來做一個的入口讓我們在文件里進行的配置。如果想要顯示它們,我們可以在運行的時候使用你還可以使用,在改變代碼的時候自動進行打包。新建文件,里面是一段,告訴使用進行預處理。 本文譯自:Webpack your bags這篇文章由入門到深入的介紹了webpack的功能和使用技巧,真心值得一看。 由于我英語水平有限,而且很少翻譯文章,所以文中的一些語句在翻譯時做了類似語義的轉換,望諒解。...
摘要:一個返回值渲染后的例那年那夏我是,年齡結果我是那年那夏,年齡的思想的核心是標簽和。從上面的代碼中可以看到定義模板時,使用了這樣的標記,這就是的標簽,只不過它用替代了,以免跟標簽的混淆。 Mustache學習筆記 Mustache 是一款基于javascript 實現(xiàn)的模板引擎,類似于 Microsoft’s jQuery template plugin,但更簡單易用,在前后端分離的技術...
摘要:簡介是一個輕邏輯模板解析引擎,它的優(yōu)勢在于可以應用在等多種編程語言中。這里主要是看中的應用。 mustache簡介 Mustache 是一個 logic-less (輕邏輯)模板解析引擎,它的優(yōu)勢在于可以應用在 Javascript、PHP、Python、Perl 等多種編程語言中。這里主要是看JavaScript中的應用。Javascript 語言的模板引擎,目前流行有 Mustac...
閱讀 694·2021-11-15 11:37
閱讀 3316·2021-10-27 14:14
閱讀 6038·2021-09-13 10:30
閱讀 2960·2021-09-04 16:48
閱讀 1926·2021-08-18 10:22
閱讀 2125·2019-08-30 14:19
閱讀 728·2019-08-30 10:54
閱讀 1744·2019-08-29 18:40