在平時編碼中,經常要做拼接字符串的工作,如把json數據用HTML展示出來,以往字符串拼接與邏輯混在在一起會讓代碼晦澀不堪,加大了多人協作與維護的成本。而采用前端模板機制就能很好的解決這個問題。
精妙的 tmpl
前端模板類開源的不少,但最屬 jQuery 作者 John Resig 開發的 “javascript micro templating” 最為精妙,寥寥幾筆便實現了模板引擎核心功能。 它的介紹與使用方式請看作者博客:http://ejohn.org/blog/javascript-micro-templating/
麻雀雖小,五臟俱全,除了基本的數據附加外,還擁有緩存機制、邏輯支持。現在,若要我評出一個javascript 最節能的自定義函數排名,第一名是 $ 函數(document.getElementById 簡版),而第二名就是 tmpl 了。
當然,它並非完美,我使用過程中發現了一些問題:
tmpl 美中不足
一、無法正確處理轉義字符,如: tmpl('<%=name%>\\<%=id%> ', {name:'糖餅', id: '1987'});它就會報錯。若正常工作,它應該輸出:糖餅\1987
二、無法識別數據里的單引號
三、設置變量默認值復雜,如
tmpl('<%if(obj.name){%><%=name%><%}else{%>默認值<%}%> ', {name:'糖餅'}); //設置name默認為 “默認值”
tmpl 優化版本
廢話不多說,先敬上代碼:
1 function tmpl(str, data) { 2 var $ = '$' + (+ new Date) 3 , fn = function (data) { 4 var i, variable = [$], value = [[]]; 5 for (i in data) { 6 variable.push(i); 7 value.push(data[i]); 8 } 9 return (new Function(variable, fn.$)) 10 .apply(data, value).join(""); 11 }; 12 13 //將模板解析成函數 14 fn.$ = fn.$ || $ + ".push('" 15 + str.replace(/\\/g, "\\\\") 16 .replace(/'/g, "\\'") //防止單括號錯誤 17 .replace(/[\r\t\n]/g, " ") 18 .split("[:").join("\t") 19 .replace(/((^|:])[^\t]*)'/g, "$1\r") 20 .replace(/\t=([^\?]*?):]/g, "',$1,'") 21 .replace(/\t=([^\?]*?)\?(.*?):]/g, "',this.$1||'$2','") // [:=data?:] [:=data?任何內容:] 22 .split("\t").join("');") 23 .split(":]").join($ + ".push('") 24 .split("\r").join("\\'") 25 + "');return " + $; 26 27 //如果未定義data則返回編譯好的函數,使用時直接傳入數據即可, 28 //省去每次解析成函數的時間 29 return data ? fn(data) : fn; 30 31 };
好吧,上面的代碼看起來超出了4行,原諒我標題黨。不過這段代碼經過壓縮后,確實只有四行^_^。下面我們就來詳細解構它。
首先看一下使用示例:
1 //循環結構 2 3 var tpl = '[: for(var k in ary){ var one=ary[k]; :]' 4 + '<p>[:=one:]</p>' 5 + '[: } :]'; 6 var data = {ary:[123,'abc']}; 8 var div = tmpl(tpl,data); 10 console.log(div); //</p>123</p><p>abc</p> 11 12 13 //變量驗證 14 15 var tpl = '[: if(this.name!==undefined){ :]' //注意必須使用 this.name,直接使用name,如果未定義就會報錯 16 + '<p>[:=name:]</p>' 17 + '[: } :]'; 18 var data = {name:'abc'}; 20 var div = tmpl(tpl,data); 22
23 //你還可以這樣方便地使用未定義的變量: 24
25 var tpl = '<p>name:[:=name?:], name:[:=name?默認值:]</p>'; 26 var data = {no:'abc'}; 27 var div = tmpl(tpl,data); 28 29 console.log(div); // <p>name:, name:默認值</p> 30 31 32 //緩存編譯結果 33 34 35 var tpl = '[: for(var k in ary){ var one=ary[k]; :]' 36 + '<p>[:=one:]</p>' 37 + '[: } :]'; 38 var data = {ary:[123,'abc']}; 40 var render = tmpl(tpl); //不傳入data,則生成緩存,多次使用緩存節約大量正則運算 42 var div = render(data); //傳入data,代入變量,解析成最終結果 43 44 console.log(div); //<p>123</p><p>abc</p>
使用方法:在 [: 與 :] 之間使用任何js代碼,並且通過 [:=data:] 方式以字符串形式輸出變量。更加詳細的使用方法/手冊,請查看:http://docs.codekart.jojoin.com/p/tool_tmpl
優化的地方:
一. 正確處理轉義字符 \ ' 等轉義字符
二. 修改包裹符 <% %> 為 [: :] 防止與 html標簽</>和求余運算符%產生沖突。
三. 修改環境變量 obj 為 this
tmpl('<%if(obj.name=="name")%>') //舊版本
tmpl('[: if(this.name=="name") :]') //新版本
四. 為變量添加默認值
tmpl('<%if(obj.name){%><%=name%><%}else{%>默認值<%}%>') //舊版本
tmpl('[:=name?默認值:]') //新版本
五. 去掉 with 語句,大幅提升引擎性能
六. 刪除可有可無的功能,保持精簡
七. 增加調試模式
//打印模板編譯中間結果:
console(tmpl('<p>[:=name:]</p>').$);
//$1408707567855.push('<p>',name,'</p>');return $1408707567855
此引擎函數大致分為兩部分:
一. 上半部分:模板函數解析執行
二. 下半部分:正則運算生成模板函數
可以看出,引擎實現的重點主要在下半部分的一堆正則表達式,也就是“模板編譯”的過程。這里我不打算把每一個正則的功能都說清楚,那樣篇幅太大(好吧是我懶),各位看官請自行閱讀研究。
另外,此模板引擎已被集成到 Node.js web開發框架 Codekart 中。
Codekart 是一套給 Node.js 開發者使用的應用程序開發框架和工具包。 它提供一套豐富的標准庫以及簡單的接口和邏輯結構, 其目的是使開發人員更快速地進行項目開發。 使用 Codekart 可以減少代碼的編寫量, 並將你的精力投入到項目的創造性開發上。
它已經幫你出色的完成了下面這些事情:
優雅的框架思維
如果你需要一個真正的框架,而不是一個模塊/中間件/工具箱,如果你需要簡約與便捷,需要一目了然、理所當然的舒適感,那么 Codekart 將是最好的選擇。
高性能 HTTP 服務器
Codekart 處理 http 請求的性能接近原生 Node.js 代碼: http.createServer(), 原因是框架只是對此函數做了簡單的封裝,其性能的損耗僅僅只有一個 url 正則匹配運算,路由請求處理程序。
便捷的靜態文件服務器
把文件放入 static/ 目錄下,啟動Codekart,url 訪問,搞定!
web 頁面模塊化支持
實際上,這是Codekart最出色的部分!它是前后端一體化的,可以像寫配置文件一樣編寫web頁面, 框架自動完成 js、css 、tpl 文件的模塊化加載、合並、壓縮, 並在html里引用,自動完成 html 模板的解析,並且支持頁面繼承和多態,一切就是那么簡單輕松!
豐富的工具箱
Codekart 准備了一系列強大的前后端工具集合,涉及進程通信,數據緩存,文件讀取,文件上傳,數據采集與處理,流程控制,任務計划等諸多方面。
框架源碼托管在Github:https://github.com/yangjiePro/Codekart 歡迎提交新的代碼!
