首先咱們來看一個簡單模板:javascript
<script type="template" id="template"> <h2> <a href="{{href}}"> {{title}} </a> </h2> <img src="{{imgSrc}}" alt="{{title}}"> </script>
其中被{{ xxx }}包含的就是咱們要替換的變量。
接着咱們可能經過ajax或者其餘方法得到數據。這裏咱們本身定義了數據,具體以下:css
var data = [ { title: "Create a Sticky Note Effect in 5 Easy Steps with CSS3 and HTML5", href: "http://net.tutsplus.com/tutorials/html-css-techniques/create-a-sticky-note-effect-in-5-easy-steps-with-css3-and-html5/", imgSrc: "https://d2o0t5hpnwv4c1.cloudfront.net/771_sticky/sticky_notes.jpg" }, { title: "Nettuts+ Quiz #8", href: "http://net.tutsplus.com/articles/quizzes/nettuts-quiz-8-abbreviations-darth-sidious-edition/", imgSrc: "https://d2o0t5hpnwv4c1.cloudfront.net/989_quiz2jquerybasics/quiz.jpg" } ];
ok,如今的問題就是咱們怎麼把數據導入到模板裏面呢?html
第一種你們會想到的就是採用replace直接替換裏面的變量:前端
template = document.querySelector('#template').innerHTML, result = document.querySelector('.result'), i = 0, len = data.length, fragment = ''; for ( ; i < len; i++ ) { fragment += template .replace( /\{\{title\}\}/, data[i].title ) .replace( /\{\{href\}\}/, data[i].href ) .replace( /\{\{imgSrc\}\}/, data[i].imgSrc ); } result.innerHTML = fragment;
第二種的話,相對第一種比較靈活,採用的是正則替換,對於初級前端,不少人對正則掌握的並非很好,通常也用的比較少。具體實現以下:html5
template = document.querySelector('#template').innerHTML, result = document.querySelector('.result'), attachTemplateToData; // 將模板和數據做爲參數,經過數據裏全部的項將值替換到模板的標籤上(注意不是遍歷模板標籤,由於標籤可能不在數據裏存在)。 attachTemplateToData = function(template, data) { var i = 0, len = data.length, fragment = ''; // 遍歷數據集合裏的每個項,作相應的替換 function replace(obj) { var t, key, reg; //遍歷該數據項下全部的屬性,將該屬性做爲key值來查找標籤,而後替換 for (key in obj) { reg = new RegExp('{{' + key + '}}', 'ig'); t = (t || template).replace(reg, obj[key]); } return t; } for (; i < len; i++) { fragment += replace(data[i]); } return fragment; }; result.innerHTML = attachTemplateToData(template, data);
與第一種相比較,第二種代碼看上去多了,可是功能實則更爲強大了。第一種咱們須要每次從新編寫變量名,若是變量名比較多的話,會比較麻煩,且容易出錯。第二種的就沒有這些煩惱。java
經過上面的例子,你們對模板引擎應該有個初步的認識了,下面咱們來說解一些相關知識。node
模板通常都是放置到 textarea/input 等表單控件,或者 script 等標籤中。好比上面的例子,咱們就是放在 script 標籤上的。jquery
通常都是經過ID來獲取,document.getElementById(「ID」):css3
//textarea或input則取value,其它狀況取innerHTML var html = /^(textarea|input)$/i.test(element.nodeName) ? element.value : element.innerHTML;
上面的是通用的模板獲取方法,這樣無論你是放在 textarea/input 仍是 script 標籤下均可以獲取到。ajax
通常都是templateFun("id", data);其中id爲存放模板字符串的元素id,data爲須要裝載的數據。
模板解析主要是指將模板中 JavaScript 語句和 html 分離出來,編譯的話將模板字符串編譯成最終的模板。上面的例子比較簡單,尚未涉及到模板引擎的核心。
要指出的是,不一樣的模板引擎所用的分隔符多是不同,上面的例子用的是{{ }},而Jquery tmpl 使用的是<% %>。
jQuery tmpl是由jQuery的做者寫的,代碼短小精悍。總共20多行,功能卻比咱們上面的強大不少。咱們先來看一看源碼:
(function(){ var cache = {}; this.tmpl = function tmpl(str, data){ var fn = !/\W/.test(str) ? cache[str] = cache[str] || tmpl(document.getElementById(str).innerHTML) : new Function("obj", "var p=[],print=function(){p.push.apply(p,arguments);};" + "with(obj){p.push('" + str .replace(/[\r\t\n]/g, " ") .split("<%").join("\t") .replace(/((^|%>)[^\t]*)'/g, "$1\r") .replace(/\t=(.*?)%>/g, "',$1,'") .split("\t").join("');") .split("%>").join("p.push('") .split("\r").join("\\'") + "');}return p.join('');"); return data ? fn( data ) : fn; }; })();
初看是否是以爲有點懵,徹底不能理解的代碼。沒事,後面咱們會對源碼進行解釋的,咱們仍是先看一下所用的模板
<ul> <% for ( var i = 0; i < users.length; i++ ) { %> <li><a href="<%=users[i].url%>"><%=users[i].name%></a></li> <% } %> </ul>
能夠發現,這個模板比入門例子的模板更爲複雜,由於裏面還夾雜着 JavaScript 代碼。JavaScript 代碼採用 <% %> 包含。而要替換的變量則是用 <%= %> 分隔開的。
下面我再來對代碼作個註釋。不過即便看了註釋,你也不必定能很快理解,最好的辦法是本身實際動手操做一遍。
// 代碼整個放在一個當即執行函數裏面
(function(){ // 用來緩存,有時候一個模板要用屢次,這時候,咱們直接用緩存就會很方便
var cache = {};
// tmpl綁定在this上,這裏的this值得是window this.tmpl = function tmpl(str, data){
// 只有模板纔有非字母數字字符,用來判斷傳入的是模板id仍是模板字符串,
// 若是是id的話,判斷是否有緩存,沒有緩存的話調用tmpl;
// 若是是模板的話,就調用new Function()解析編譯 var fn = !/\W/.test(str) ? cache[str] = cache[str] || tmpl(document.getElementById(str).innerHTML) : new Function("obj",
// 注意這裏整個是字符串,經過 + 號拼接 "var p=[],print=function(){p.push.apply(p,arguments);};" + "with(obj){p.push('" + str
// 去除換行製表符\t\n\r .replace(/[\r\t\n]/g, " ")
// 將左分隔符變成 \t .split("<%").join("\t")
// 去掉模板中單引號的干擾 .replace(/((^|%>)[^\t]*)'/g, "$1\r")
// 爲 html 中的變量變成 ",xxx," 的形式, 如:\t=users[i].url%> 變成 ',users[i].url,'
// 注意這裏只有一個單引號,還不配對 .replace(/\t=(.*?)%>/g, "',$1,'")
// 這時候,只有JavaScript 語句前面纔有 "\t", 將 \t 變成 ');
// 這樣就可把 html 標籤添加到數組p中,而javascript 語句 不須要 push 到裏面。
.split("\t").join("');")
// 這時候,只有JavaScript 語句後面纔有 "%>", 將 %> 變成 p.push('
// 上一步咱們再 html 標籤後加了 ');, 因此要把 p.push(' 語句放在 html 標籤放在前面,這樣就能夠變成 JavaScript 語句 .split("%>").join("p.push('")
// 將上面可能出現的干擾的單引號進行轉義
.split("\r").join("\\'")
// 將數組 p 變成字符串。 + "');}return p.join('');"); return data ? fn( data ) : fn; }; })();
上面代碼中,有一個要指出的就是new Function 的使用 方法。給 new Function() 傳一個字符串做爲函數的body來構造一個 JavaScript函數。編程中並不常常用到,但有時候應該是頗有用的。
下面是 new Function 的基本用法:
// 最後一個參數是函數的 body(函數體),類型爲 string; // 前面的參數都是 索要構造的函數的參數(名字) var myFunction = new Function('users', 'salary', 'return users * salary');
最後的字符串就是下面這種形式:
var p = [], print = function() { p.push.apply(p, arguments); }; with(obj) { p.push(' <ul> '); for (var i = 0; i < users.length; i++) { p.push(' <li><a href="', users[i].url, '">', users[i].name, '</a></li> '); } p.push(' </ul> '); } return p.join('');
裏面的 print 函數 在咱們的模板裏面是沒有用到的。
要指出的是,採用 push 的方法在 IE6-8 的瀏覽器下會比 += 的形式快,可是在如今的瀏覽器裏面, += 是拼接字符串最快的方法。實測代表現代瀏覽器使用 += 會比數組 push 方法快,而在 v8 引擎中,使用 += 方式比數組拼接快 4.7 倍。因此 目前有些更高級的模板引擎會 根據 javascript 引擎特性採用了兩種不一樣的字符串拼接方式。
下面的代碼是摘自騰訊的 artTemplate 的, 根據瀏覽器的類型來選擇不一樣的拼接方式。功能越強大,所考慮的問題也會更多。
var isNewEngine = ''.trim;// '__proto__' in {} var replaces = isNewEngine ? ["$out='';", "$out+=", ";", "$out"] : ["$out=[];", "$out.push(", ");", "$out.join('')"];
挑戰:有興趣的能夠改用 += 來實現上面的代碼。
模板引擎原理總結起來就是:先獲取html中對應的id下得innerHTML,利用開始標籤和關閉標籤進行字符串切分,實際上是將模板劃分紅兩部分內容,一部分是html部分,一部分是邏輯部分,經過區別一些特殊符號好比each、if等來將字符串拼接成函數式的字符串,將兩部分各自通過處理後,再次拼接到一塊兒,最後將拼接好的字符串採用new Function()的方式轉化成所須要的函數。
目前模板引擎的種類繁多,功能也愈來愈強大,不一樣模板間實現原理大同小異,各有優缺,請按需選擇。
參考文章: