JavaScript模板引擎實(shí)現(xiàn)原理實(shí)例詳解
本文實(shí)例講述了JavaScript模板引擎實(shí)現(xiàn)原理。分享給大家供大家參考,具體如下:
1、入門(mén)實(shí)例
首先我們來(lái)看一個(gè)簡(jiǎn)單模板:
<script type="template" id="template"> <h2> <a href="{{href}}" rel="external nofollow" > {{title}} </a> </h2> <img src="{{imgSrc}}" alt="{{title}}"> </script>
其中被{{ xxx }}
包含的就是我們要替換的變量。
接著我們可能通過(guò)ajax或者其他方法獲得數(shù)據(jù)。這里我們自己定義了數(shù)據(jù),具體如下:
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,現(xiàn)在的問(wèn)題就是我們?cè)趺窗褦?shù)據(jù)導(dǎo)入到模板里面呢?
第一種大家會(huì)想到的就是采用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;
第二種的話,相對(duì)第一種比較靈活,采用的是正則替換,對(duì)于初級(jí)前端,很多人對(duì)正則掌握的并不是很好,一般也用的比較少。具體實(shí)現(xiàn)如下:
template = document.querySelector('#template').innerHTML, result = document.querySelector('.result'), attachTemplateToData; // 將模板和數(shù)據(jù)作為參數(shù),通過(guò)數(shù)據(jù)里所有的項(xiàng)將值替換到模板的標(biāo)簽上(注意不是遍歷模板標(biāo)簽,因?yàn)闃?biāo)簽可能不在數(shù)據(jù)里存在)。 attachTemplateToData = function(template, data) { var i = 0, len = data.length, fragment = ''; // 遍歷數(shù)據(jù)集合里的每一個(gè)項(xiàng),做相應(yīng)的替換 function replace(obj) { var t, key, reg; //遍歷該數(shù)據(jù)項(xiàng)下所有的屬性,將該屬性作為key值來(lái)查找標(biāo)簽,然后替換 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);
與第一種相比較,第二種代碼看上去多了,但是功能實(shí)則更為強(qiáng)大了。第一種我們需要每次重新編寫(xiě)變量名,如果變量名比較多的話,會(huì)比較麻煩,且容易出錯(cuò)。第二種的就沒(méi)有這些煩惱。
2、模板引擎相關(guān)知識(shí)
通過(guò)上面的例子,大家對(duì)模板引擎應(yīng)該有個(gè)初步的認(rèn)識(shí)了,下面我們來(lái)講解一些相關(guān)知識(shí)。
2.1 模板存放
模板一般都是放置到 textarea/input 等表單控件,或者 script 等標(biāo)簽中。比如上面的例子,我們就是放在 script 標(biāo)簽上的。
2.2 模板獲取
一般都是通過(guò)ID來(lái)獲取,document.getElementById("ID"):
//textarea或input則取value,其它情況取innerHTML var html = /^(textarea|input)$/i.test(element.nodeName) ? element.value : element.innerHTML;
上面的是通用的模板獲取方法,這樣不管你是放在 textarea/input 還是 script 標(biāo)簽下都可以獲取到。
2.3 模板函數(shù)
一般都是templateFun("id", data);
其中id為存放模板字符串的元素id,data為需要裝載的數(shù)據(jù)。
2.4 模板解析編譯
模板解析主要是指將模板中 JavaScript 語(yǔ)句和 html 分離出來(lái),編譯的話將模板字符串編譯成最終的模板。上面的例子比較簡(jiǎn)單,還沒(méi)有涉及到模板引擎的核心。
2.5 模板分隔符
要指出的是,不同的模板引擎所用的分隔符可能是不一樣,上面的例子用的是{{ }},而Jquery tmpl 使用的是<% %>。
3、jQuery tmpl 實(shí)現(xiàn)原理解析
jQuery tmpl是由jQuery的作者寫(xiě)的,代碼短小精悍。總共20多行,功能卻比我們上面的強(qiáng)大很多。我們先來(lái)看一看源碼:
(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; }; })();
初看是不是覺(jué)得有點(diǎn)懵,完全不能理解的代碼。沒(méi)事,后面我們會(huì)對(duì)源碼進(jìn)行解釋的,我們還是先看一下所用的模板
<ul> <% for ( var i = 0; i < users.length; i++ ) { %> <li><a href="<%=users[i].url%>" rel="external nofollow" ><%=users[i].name%></a></li> <% } %> </ul>
可以發(fā)現(xiàn),這個(gè)模板比入門(mén)例子的模板更為復(fù)雜,因?yàn)槔锩孢€夾雜著 JavaScript 代碼。JavaScript 代碼采用 <% %> 包含。而要替換的變量則是用 <%= %> 分隔開(kāi)的。
下面我再來(lái)對(duì)代碼做個(gè)注釋。不過(guò)即使看了注釋,你也不一定能很快理解,最好的辦法是自己實(shí)際動(dòng)手操作一遍。
// 代碼整個(gè)放在一個(gè)立即執(zhí)行函數(shù)里面 (function(){ // 用來(lái)緩存,有時(shí)候一個(gè)模板要用多次,這時(shí)候,我們直接用緩存就會(huì)很方便 var cache = {}; // tmpl綁定在this上,這里的this值得是window this.tmpl = function tmpl(str, data){ // 只有模板才有非字母數(shù)字字符,用來(lái)判斷傳入的是模板id還是模板字符串, // 如果是id的話,判斷是否有緩存,沒(méi)有緩存的話調(diào)用tmpl; // 如果是模板的話,就調(diào)用new Function()解析編譯 var fn = !/\W/.test(str) ? cache[str] = cache[str] || tmpl(document.getElementById(str).innerHTML) : new Function("obj", // 注意這里整個(gè)是字符串,通過(guò) + 號(hào)拼接 "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") // 去掉模板中單引號(hào)的干擾 .replace(/((^|%>)[^\t]*)'/g, "$1\r") // 為 html 中的變量變成 ",xxx," 的形式, 如:\t=users[i].url%> 變成 ',users[i].url,' // 注意這里只有一個(gè)單引號(hào),還不配對(duì) .replace(/\t=(.*?)%>/g, "',$1,'") // 這時(shí)候,只有JavaScript 語(yǔ)句前面才有 "\t", 將 \t 變成 '); // 這樣就可把 html 標(biāo)簽添加到數(shù)組p中,而javascript 語(yǔ)句 不需要 push 到里面。 .split("\t").join("');") // 這時(shí)候,只有JavaScript 語(yǔ)句后面才有 "%>", 將 %> 變成 p.push(' // 上一步我們?cè)?html 標(biāo)簽后加了 ');, 所以要把 p.push(' 語(yǔ)句放在 html 標(biāo)簽放在前面,這樣就可以變成 JavaScript 語(yǔ)句 .split("%>").join("p.push('") // 將上面可能出現(xiàn)的干擾的單引號(hào)進(jìn)行轉(zhuǎn)義 .split("\r").join("\\'") // 將數(shù)組 p 變成字符串。 + "');}return p.join('');"); return data ? fn( data ) : fn; }; })();
上面代碼中,有一個(gè)要指出的就是new Function
的使用 方法。給 new Function()
傳一個(gè)字符串作為函數(shù)的body來(lái)構(gòu)造一個(gè) JavaScript函數(shù)。編程中并不經(jīng)常用到,但有時(shí)候應(yīng)該是很有用的。
下面是 new Function
的基本用法:
// 最后一個(gè)參數(shù)是函數(shù)的 body(函數(shù)體),類型為 string; // 前面的參數(shù)都是 索要構(gòu)造的函數(shù)的參數(shù)(名字) 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, '" rel="external nofollow" >', users[i].name, '</a></li> '); } p.push(' </ul> '); } return p.join('');
里面的 print 函數(shù) 在我們的模板里面是沒(méi)有用到的。
要指出的是,采用 push 的方法在 IE6-8 的瀏覽器下會(huì)比 += 的形式快,但是在現(xiàn)在的瀏覽器里面, += 是拼接字符串最快的方法。實(shí)測(cè)表明現(xiàn)代瀏覽器使用 += 會(huì)比數(shù)組 push 方法快,而在 v8 引擎中,使用 += 方式比數(shù)組拼接快 4.7 倍。所以 目前有些更高級(jí)的模板引擎會(huì) 根據(jù) javascript 引擎特性采用了兩種不同的字符串拼接方式。
下面的代碼是摘自騰訊的 artTemplate 的, 根據(jù)瀏覽器的類型來(lái)選擇不同的拼接方式。功能越強(qiáng)大,所考慮的問(wèn)題也會(huì)更多。
var isNewEngine = ''.trim;// '__proto__' in {} var replaces = isNewEngine ? ["$out='';", "$out+=", ";", "$out"] : ["$out=[];", "$out.push(", ");", "$out.join('')"];
挑戰(zhàn):有興趣的可以改用 += 來(lái)實(shí)現(xiàn)上面的代碼。
總結(jié)
模板引擎原理總結(jié)起來(lái)就是:先獲取html中對(duì)應(yīng)的id下得innerHTML,利用開(kāi)始標(biāo)簽和關(guān)閉標(biāo)簽進(jìn)行字符串切分,其實(shí)是將模板劃分成兩部份內(nèi)容,一部分是html部分,一部分是邏輯部分,通過(guò)區(qū)別一些特殊符號(hào)比如each、if等來(lái)將字符串拼接成函數(shù)式的字符串,將兩部分各自經(jīng)過(guò)處理后,再次拼接到一起,最后將拼接好的字符串采用new Function()
的方式轉(zhuǎn)化成所需要的函數(shù)。
目前模板引擎的種類繁多,功能也越來(lái)越強(qiáng)大,不同模板間實(shí)現(xiàn)原理大同小異,各有優(yōu)缺,請(qǐng)按需選擇。
參考文章:
1、Quick Tip: Create a Makeshift JavaScript Templating Solution
2、JavaScript模板引擎的應(yīng)用場(chǎng)景及實(shí)現(xiàn)原理
更多關(guān)于JavaScript相關(guān)內(nèi)容可查看本站專題:《javascript面向?qū)ο笕腴T(mén)教程》、《JavaScript切換特效與技巧總結(jié)》、《JavaScript查找算法技巧總結(jié)》、《JavaScript錯(cuò)誤與調(diào)試技巧總結(jié)》、《JavaScript數(shù)據(jù)結(jié)構(gòu)與算法技巧總結(jié)》、《JavaScript遍歷算法與技巧總結(jié)》及《JavaScript數(shù)學(xué)運(yùn)算用法總結(jié)》
希望本文所述對(duì)大家JavaScript程序設(shè)計(jì)有所幫助。
相關(guān)文章
JavaScript限制在客戶區(qū)可見(jiàn)范圍的拖拽(解決scrollLeft和scrollTop的問(wèn)題)(2)
這篇文章主要介紹了JavaScript限制在客戶區(qū)可見(jiàn)范圍的拖拽,解決scrollLeft和scrollTop的問(wèn)題,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-05-05JS中的數(shù)組轉(zhuǎn)變成JSON格式字符串的方法
這篇文章主要介紹了JS中的數(shù)組轉(zhuǎn)變成JSON格式字符串的方法,需要的朋友可以參考下2017-05-05JavaScript高級(jí)程序設(shè)計(jì) 事件學(xué)習(xí)筆記
JavaScript高級(jí)程序設(shè)計(jì) 事件學(xué)習(xí)筆記,需要的朋友可以參考下。2011-09-09Javascript實(shí)現(xiàn)倒計(jì)時(shí)時(shí)差效果
這篇文章主要為大家詳細(xì)介紹了Javascript實(shí)現(xiàn)倒計(jì)時(shí)時(shí)差效果,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-05-05關(guān)閉頁(yè)面window.location事件未執(zhí)行的原因及解決方法
這篇文章主要介紹了關(guān)閉頁(yè)面window.location事件未執(zhí)行的原因及解決方法,需要的朋友可以參考下2014-09-09js 創(chuàng)建對(duì)象 經(jīng)典模式全面了解
下面小編就為大家?guī)?lái)一篇js 創(chuàng)建對(duì)象 經(jīng)典模式全面了解。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2016-08-08javascript 通過(guò)封裝div方式彈出div窗體
廢話少說(shuō),此js對(duì)象是通過(guò)封裝頁(yè)面上的div,將其彈出,可以彈出多個(gè),參考了一些高人代碼,達(dá)到我要的效果。先看看效果圖。配合一css就可以很好看了。2009-10-10