摘要:具體可配置的項可以參看其源代碼。那引擎對象是如何被構(gòu)造出來的呢看這句由此,我們進(jìn)入了的核心構(gòu)造函數(shù),。由于該構(gòu)造函數(shù)篇幅很長,我們先看下簡略版的結(jié)構(gòu),然后拆開來分析。此外,推薦使用注冊自定義函數(shù),而非使用。
Juicer.js源碼解讀
Version: 0.6.9-stable
Date: 8th of Aug, 2015
個人能力有限,如有分析不當(dāng)?shù)牡胤剑瑧┱堉刚?/p> 第一部分: 參數(shù)配置
方法與參數(shù)
參數(shù)配置方法是 juicer.set,該方法接受兩個參數(shù)或一個參數(shù):
當(dāng)傳入兩個參數(shù)時,如 juicer.set("cache",false) ,即是設(shè)置 cache 為 false
當(dāng)傳入一個參數(shù)時,該參數(shù)應(yīng)為一個對象,如 juicer.set({cache:false}),系統(tǒng)將遍歷這個對象的屬性來設(shè)值
可以配置的內(nèi)容
我們可以配置一些參數(shù)選項,包括 cache、strip、errorhandling、detection;其默認(rèn)值都是true;我們還可以修改模板的語法邊界符,如 tag::operationOpen 等。具體可配置的項可以參看其源代碼。
工作原理
juicer.options = { // 是否緩存模板編譯結(jié)果 cache: true, // 是否清除空白 strip: true, // 是否處理錯誤 errorhandling: true, // 是否檢測變量是否定義 detection: true, // 自定義函數(shù)庫 _method: __creator({ __escapehtml: __escapehtml, __throw: __throw, __juicer: juicer }, {}) };
選項解析如下:
cache 是否緩存編譯結(jié)果(引擎對象)。緩存的結(jié)果存于 juicer.__cache
strip 是否清除模板中的空白,包括換行、回車等
errorhandling 是否處理錯誤
detection 開啟后,如果變量未定義,將用空白字符串代替變量位置,否則照常輸出,所以如果關(guān)閉此項,有可能造成輸出 undefined
_method 存儲的是用戶注冊的自定義函數(shù),系統(tǒng)內(nèi)部創(chuàng)建的自定義函數(shù)或?qū)ο笥?__escapehtml 處理HTML轉(zhuǎn)義、__throw 拋出錯誤、__juicer引用 juicer。__creator 方法本文最末講解
在 Node.js 環(huán)境中,cache 默認(rèn)值是 false,請看下面代碼
if(typeof(global) !== "undefined" && typeof(window) === "undefined") { juicer.set("cache", false); }
這段代碼在結(jié)尾處可以找到。
此外,還有一個屬性是 juicer.options.loose,默認(rèn)值為 undefined(沒有設(shè)置),當(dāng)其值不為 false(此亦系統(tǒng)默認(rèn))時,將對 {@each}、{@if}、{@else if}、${}、{@include}等中的變量名和自定義函數(shù)名進(jìn)行校驗,給其中使用到的變量、函數(shù)定義并添加到模板的開頭,以保證能夠順利使用。
所以,如果我們更改此設(shè)置,可能造成系統(tǒng)錯誤
// 這些操作應(yīng)當(dāng)避免,否則會造成系統(tǒng)錯誤 // 將`juicer.options.loose`設(shè)為`false` // juicer.set("loose",false);
下面來看 juicer.set 方法的源代碼
juicer.set = function(conf, value) { // 引用`juicer` var that = this; // 反斜杠轉(zhuǎn)義 var escapePattern = function(v) { // 匹配 $ ( [ ] + ^ { } ? * | . * // 這些符號都需要被轉(zhuǎn)義 return v.replace(/[$()[]+^{}?*|.]/igm, function($) { return "" + $; }); }; // 設(shè)置函數(shù) var set = function(conf, value) { // 語法邊界符匹配 var tag = conf.match(/^tag::(.*)$/i); if(tag) { // 由于系統(tǒng)這里沒有判斷語法邊界符是否是系統(tǒng)所用的 // 所以一定要拼寫正確 that.tags[tag[1]] = escapePattern(value); // 重新生成匹配正則 // `juicer.tagInit`解析見下面 that.tagInit(); return; } // 其他配置項 that.options[conf] = value; }; // 如果傳入兩個參數(shù),`conf`表示要修改的屬性,`value`是要修改的值 if(arguments.length === 2) { set(conf, value); return; } // 如果傳入一個參數(shù),且是對象 if(conf === Object(conf)) { // 遍歷該對象的自有屬性設(shè)置 for(var i in conf) { if(conf.hasOwnProperty(i)) { set(i, conf[i]); } } } };
注釋里面已經(jīng)提示,通過 juicer.set 方法可以覆蓋任何屬性。
如果修改了語法邊界符設(shè)定,將會重新生成匹配正則,下面看匹配正則的源代碼
juicer.tags = { // 操作開 operationOpen: "{@", // 操作閉 operationClose: "}", // 變量開 interpolateOpen: "${", // 變量閉標(biāo)簽 interpolateClose: "}", // 禁止對其內(nèi)容轉(zhuǎn)義的變量開 noneencodeOpen: "$${", // 禁止對其內(nèi)容轉(zhuǎn)義的變量閉 noneencodeClose: "}", // 注釋開 commentOpen: "{#", // 注釋閉 commentClose: "}" }; juicer.tagInit = function() { /** * 匹配each循環(huán)開始,以下都是OK的 * `each VAR as VALUE`, 如 {@each names as name} * `each VAR as VALUE ,INDEX`,如 {@each names as name,key} * `each VAR as`,如 {@each names as} * 需要說明后兩種情況: * `,key` 是一起被捕獲的,所以在編譯模板的時候,系統(tǒng)會用`substr`去掉`,` * as 后沒有指定別名的話,默認(rèn)以`value`為別名,所以 * {@each names as} 等價于 {@each names as value} */ var forstart = juicer.tags.operationOpen + "eachs*([^}]*?)s*ass*(w*?)s*(,s*w*?)?" + juicer.tags.operationClose; // each循環(huán)結(jié)束 var forend = juicer.tags.operationOpen + "/each" + juicer.tags.operationClose; // if條件開始 var ifstart = juicer.tags.operationOpen + "ifs*([^}]*?)" + juicer.tags.operationClose; // if條件結(jié)束 var ifend = juicer.tags.operationOpen + "/if" + juicer.tags.operationClose; // else條件開始 var elsestart = juicer.tags.operationOpen + "else" + juicer.tags.operationClose; // eles if 條件開始 var elseifstart = juicer.tags.operationOpen + "else ifs*([^}]*?)" + juicer.tags.operationClose; // 匹配變量 var interpolate = juicer.tags.interpolateOpen + "([sS]+?)" + juicer.tags.interpolateClose; // 匹配不對其內(nèi)容轉(zhuǎn)義的變量 var noneencode = juicer.tags.noneencodeOpen + "([sS]+?)" + juicer.tags.noneencodeClose; // 匹配模板內(nèi)容注釋 var inlinecomment = juicer.tags.commentOpen + "[^}]*?" + juicer.tags.commentClose; // for輔助循環(huán) var rangestart = juicer.tags.operationOpen + "eachs*(w*?)s*ins*range(([^}]+?)s*,s*([^}]+?))" + juicer.tags.operationClose; // 引入子模板 var include = juicer.tags.operationOpen + "includes*([^}]*?)s*,s*([^}]*?)" + juicer.tags.operationClose; // 內(nèi)聯(lián)輔助函數(shù)開始 var helperRegisterStart = juicer.tags.operationOpen + "helpers*([^}]*?)s*" + juicer.tags.operationClose; // 輔助函數(shù)代碼塊內(nèi)語句 var helperRegisterBody = "([sS]*?)"; // 輔助函數(shù)結(jié)束 var helperRegisterEnd = juicer.tags.operationOpen + "/helper" + juicer.tags.operationClose; juicer.settings.forstart = new RegExp(forstart, "igm"); juicer.settings.forend = new RegExp(forend, "igm"); juicer.settings.ifstart = new RegExp(ifstart, "igm"); juicer.settings.ifend = new RegExp(ifend, "igm"); juicer.settings.elsestart = new RegExp(elsestart, "igm"); juicer.settings.elseifstart = new RegExp(elseifstart, "igm"); juicer.settings.interpolate = new RegExp(interpolate, "igm"); juicer.settings.noneencode = new RegExp(noneencode, "igm"); juicer.settings.inlinecomment = new RegExp(inlinecomment, "igm"); juicer.settings.rangestart = new RegExp(rangestart, "igm"); juicer.settings.include = new RegExp(include, "igm"); juicer.settings.helperRegister = new RegExp(helperRegisterStart + helperRegisterBody + helperRegisterEnd, "igm"); };
具體語法邊界符的用法請參照官方文檔:http://www.juicer.name/docs/docs_zh_cn.html
一般地,不建議對默認(rèn)標(biāo)簽進(jìn)行修改。當(dāng)然,如果默認(rèn)語法邊界符規(guī)則與正在使用的其他語言語法規(guī)則沖突,修改 juicer 的語法邊界符就很有用了。
需要注意,{@each names as} 等價于 {@each names as value},盡管我們?nèi)砸3终_書寫的規(guī)則,避免利用系統(tǒng)自動糾錯機(jī)制
// 如下模板的寫法是不推薦的 /** {@each list as} ${value.title} {@/each} */第二部分: 注冊自定義函數(shù)
上面說,juicer.options._method 存儲了用戶的自定義函數(shù),那么我們?nèi)绾巫砸约叭绾问褂米远x函數(shù)呢?
注冊/銷自定義函數(shù)
juicer.register 方法用來注冊自定義函數(shù)
juicer.unregister 方法用來注銷自定義函數(shù)
// `fname`為函數(shù)名,`fn`為函數(shù) juicer.register = function(fname, fn) { // 自定義函數(shù)均存儲于 `juicer.options._method` // 如果已經(jīng)注冊了該函數(shù),不允許覆蓋 if(_method.hasOwnProperty(fname)) { return false; } // 將新函數(shù)注冊進(jìn)入 return _method[fname] = fn; }; juicer.unregister = function(fname) { var _method = this.options._method; // 沒有檢測是否注銷的是系統(tǒng)自定義函數(shù) // 用戶不要注銷錯了 if(_method.hasOwnProperty(fname)) { return delete _method[fname]; } };
自定義函數(shù)都是存儲在juicer.options._method中的,因此以下方法可以跳過函數(shù)是否注冊的檢驗強(qiáng)行更改自定義函數(shù),這些操作很危險:
// 這些操作應(yīng)當(dāng)避免,否則會造成系統(tǒng)錯誤 // 改變`juicer.options._method` // juicer.set("_method",{}); // juicer.unregister("__juicer"); // juicer.unregister("__throw"); // juicer.unregister("__escapehtml");第三部分: 編譯模板
先看下 juicer 的定義部分。
var juicer = function() { // 將傳遞參數(shù)(偽數(shù)組)切成數(shù)組后返回給`args`,以便調(diào)用數(shù)組的方法 var args = [].slice.call(arguments); // 將`juicer.options`推入`args`,表示渲染使用當(dāng)前設(shè)置 args.push(juicer.options); /** * 下面將獲取模板內(nèi)容 * 模板內(nèi)容取決于我們傳遞給`juicer`函數(shù)的首參數(shù) * 可以是模板節(jié)點的id屬性值 * 也可以是模板內(nèi)容本 */ // 首先會試著匹配,匹配成功就先當(dāng)作id處理 // 左右兩側(cè)的空白會被忽略 // 如果是`#`開頭,后面跟著字母、數(shù)字、下劃線、短橫線、冒號、點號都可匹配 // 所以這么寫都是可以的:`id=":-."` if(args[0].match(/^s*#([w:-.]+)s*$/igm)) { // 如果傳入的是模板節(jié)點的id,會通過`replace`方法準(zhǔn)確匹配并獲取模板內(nèi)容 // 回調(diào)函數(shù)的首參`$`是匹配的全部內(nèi)容(首參),$id是匹配的節(jié)點id args[0].replace(/^s*#([w:-.]+)s*$/igm, function($, $id) { // node.js環(huán)境沒有`document`,所以會先判斷`document` var _document = document; // 找尋節(jié)點 var elem = _document && _document.getElementById($id); // 如果該節(jié)點存在,節(jié)點的`value`或`innerHTML`就是模板內(nèi)容 // 即是說,存放模板的內(nèi)容節(jié)點只要有`value`或`innerHTML`屬性即可 //