https://github.com/hanzichi/underscore-analysis/issues/26javascript
前文 淺談 Web 中先後端模板引擎的使用 咱們簡單瞭解了模板引擎在先後端的應用場景,本文重點深刻 Underscore 的模板函數 _.template,來看看它的用法以及實現原理。html
咱們從 官方文檔 中最簡單的例子提及。前端
var compiled = _.template("hello: <%= name %>"); var html = compiled({name: 'moe'}); // hello: moe
{name: 'moe'} 模擬後臺請求到的接口數據,而變量 html 則爲拼接成的字符串,以後即可以用 innerHTML 方法加入到頁面生成 DOM。java
這一切是如何作到的?咱們能夠打印看下 compliled 方法是個什麼樣子(須要去 Underscore 源碼中打印)。git
大概是這個樣子(其實不徹底準確,真實的應該還會有個 _ 參數傳入,使得函數能用 Underscore 內部方法):github
function(obj){ var __t,__p='',__j=Array.prototype.join,print=function(){__p+=__j.call(arguments,'');}; with(obj||{}){ __p+='hello: '+ ((__t=( name ))==null?'':__t)+ ''; } return __p; }
仔細想一想,其實就是對模板字符串進行了正則解析,將須要填入數據的位置預留出來,拼接成一個字符串,用 new Function 構造一個方法(動態執行 JavaScript 字符串),方法中有大量的字符串拼接過程,而後將數據代入這個方法,返回咱們須要的 HTML 字符串。後端
盜用 木神 兩張圖,過程很是清晰。ide
_.template 支持如下三種模板。函數
1. <% %> - to execute some code 2. <%= %> - to print some value in template 3. <%- %> - to print some values HTML escaped
<% %>
裏包裹的是一些可執行的 JavaScript 語句,好比 if-else 語句,for 循環語句,等等。<%= %>
正是咱們前面使用的,會打印傳入數據相應的 key 的值,<%- %>
和前者相比,多了步 HTML 實體編碼的過程,能夠有效防止 XSS 攻擊。性能
舉個栗子:
<div></div> <script src="underscore.js"></script> <script type="text/template" id="tpl"> <ul class="list"> <% _.each(obj, function(e, i, a){ %> <% if (i === 0) %> <li><%- e.name %> <% else if (i === a.length - 1) %> <li class="last-item"><%= e.name %></li> <% else %> <li><%= e.name %></li> <% }) %> </ul> </script> <script> // mock data var data = [{name: "<script>"}, {name: "orange"}, {name: "peach"}]; var compiled = _.template(document.getElementById("tpl").innerHTML); var html = compiled(data); // console.log(html) document.querySelector("div").innerHTML = html; </script>
將數據用 li
標籤循環展現,而且將第一個值實體編碼了。
_.template 最基礎的應用就是這樣。
若是你不喜歡它默認的模板風格,也能夠本身定義,注意 key 必須和源碼中的 key 保持一致,才能覆蓋。
_.templateSettings = { // 三種渲染模板 evaluate : /<%([\s\S]+?)%>/g, interpolate : /<%=([\s\S]+?)%>/g, escape : /<%-([\s\S]+?)%>/g };
有兩種方式,一種是直接修改 _.templateSettings 變量(不推薦,修改了源碼中的變量)
_.templateSettings = { interpolate: /\{\{(.+?)\}\}/g }; var template = _.template("Hello {{ name }}!"); var ans = template({name: "Mustache"}); console.log(ans); // Hello Mustache!
比較好的方法是做爲 _.template 的第二個參數 settings 傳入:
var settings = { interpolate: /\{\{(.+?)\}\}/g // 覆蓋 _.templateSettings.interpolate }; var template = _.template("Hello {{ name }}!", settings); var ans = template({name: "Mustache"}); console.log(ans); // Hello Mustache!
咱們還能設定 settings.variable 指定 scope:
var template = _.template("Using 'with': <%= data.answer %>", {variable: 'data'}) var ans = template({answer: 'no'}); console.log(ans) // Using 'with': no
模板引擎通常都帶有預編譯功能,_.template 也不例外。
什麼是預編譯?有什麼用?
上面的代碼有兩個痛點:
性能:模板引擎渲染的時候依賴 Function 構造器實現,Function 與 eval、setTimeout、setInterval 同樣,提供了使用文本訪問 javascript 解析引擎的方法,但這樣執行 javascript 的性能很是低下。
調試:因爲是動態執行字符串,若遇到錯誤調試器沒法捕獲錯誤源,致使模板 BUG 調試變得異常痛苦。在沒有進行容錯的引擎中,局部模板若由於數據異常甚至能夠致使整個應用崩潰,隨着模板的數目增長,維護成本將劇增。
若是咱們 JavaScript 代碼中直接保存 _.template 的結果,那麼以上兩個問題就不復存在。而 _.template(jstText).source
則保存了 _.template(jstText)
返回的方法字符串。
JST is a server-side thing, not client-side. This mean that you compile Unserscore template on server side by some server-side script and save the result in a file. Then use this file as compiled Unserscore template.
關於 _.template 方法的具體實現,能夠參考樓主的 underscore-1.8.3.js 源碼解讀全文註釋版 ,全局搜索便可。
關於前端模板引擎,其實樓主也是個初學者,學習過程當中搜到的資料與你們分享下,有機會必定要用下各類模板引擎而後分析下。