溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點(diǎn)擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

JavaScript模板引擎實(shí)現(xiàn)原理實(shí)例詳解

發(fā)布時間:2020-09-20 05:31:02 來源:腳本之家 閱讀:146 作者:huansky 欄目:web開發(fā)

本文實(shí)例講述了JavaScript模板引擎實(shí)現(xiàn)原理。分享給大家供大家參考,具體如下:

1、入門實(shí)例

首先我們來看一個簡單模板:

 <script type="template" id="template">
  <h3>
   <a href="{{href}}" rel="external nofollow" >
    {{title}}
   </a>
  </h3>
  <img src="{{imgSrc}}" alt="{{title}}">
 </script>

其中被{{ xxx }}包含的就是我們要替換的變量。

接著我們可能通過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://cache.yisu.com/upload/information/20200622/114/30927.jpg"
  },
  {
   title: "Nettuts+ Quiz #8",
   href: "http://net.tutsplus.com/articles/quizzes/nettuts-quiz-8-abbreviations-darth-sidious-edition/",
   imgSrc: "https://cache.yisu.com/upload/information/20200622/114/30928.jpg"
  }
 ];

ok,現(xiàn)在的問題就是我們怎么把數(shù)據(jù)導(dǎo)入到模板里面呢?

第一種大家會想到的就是采用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;

第二種的話,相對第一種比較靈活,采用的是正則替換,對于初級前端,很多人對正則掌握的并不是很好,一般也用的比較少。具體實(shí)現(xiàn)如下:

template = document.querySelector('#template').innerHTML,
result = document.querySelector('.result'),
attachTemplateToData;
// 將模板和數(shù)據(jù)作為參數(shù),通過數(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ù)集合里的每一個項(xiàng),做相應(yīng)的替換
    function replace(obj) {
      var t, key, reg;
       
       //遍歷該數(shù)據(jù)項(xiàng)下所有的屬性,將該屬性作為key值來查找標(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)大了。第一種我們需要每次重新編寫變量名,如果變量名比較多的話,會比較麻煩,且容易出錯。第二種的就沒有這些煩惱。

2、模板引擎相關(guān)知識

通過上面的例子,大家對模板引擎應(yīng)該有個初步的認(rèn)識了,下面我們來講解一些相關(guān)知識。

2.1 模板存放

模板一般都是放置到 textarea/input 等表單控件,或者 script 等標(biāo)簽中。比如上面的例子,我們就是放在 script 標(biāo)簽上的。

2.2 模板獲取

一般都是通過ID來獲取,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 語句和 html 分離出來,編譯的話將模板字符串編譯成最終的模板。上面的例子比較簡單,還沒有涉及到模板引擎的核心。

2.5 模板分隔符

要指出的是,不同的模板引擎所用的分隔符可能是不一樣,上面的例子用的是{{ }},而Jquery tmpl 使用的是<%  %>。

3、jQuery tmpl 實(shí)現(xiàn)原理解析

jQuery tmpl是由jQuery的作者寫的,代碼短小精悍??偣?0多行,功能卻比我們上面的強(qiáng)大很多。我們先來看一看源碼:

(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;
 };
})();

初看是不是覺得有點(diǎn)懵,完全不能理解的代碼。沒事,后面我們會對源碼進(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),這個模板比入門例子的模板更為復(fù)雜,因?yàn)槔锩孢€夾雜著 JavaScript 代碼。JavaScript 代碼采用 <%  %> 包含。而要替換的變量則是用 <%=   %> 分隔開的。

下面我再來對代碼做個注釋。不過即使看了注釋,你也不一定能很快理解,最好的辦法是自己實(shí)際動手操作一遍。

// 代碼整個放在一個立即執(zhí)行函數(shù)里面
(function(){
 // 用來緩存,有時候一個模板要用多次,這時候,我們直接用緩存就會很方便
 var cache = {};
 // tmpl綁定在this上,這里的this值得是window
 this.tmpl = function tmpl(str, data){
  // 只有模板才有非字母數(shù)字字符,用來判斷傳入的是模板id還是模板字符串,
  // 如果是id的話,判斷是否有緩存,沒有緩存的話調(diào)用tmpl;
  // 如果是模板的話,就調(diào)用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 標(biāo)簽添加到數(shù)組p中,而javascript 語句 不需要 push 到里面。
      .split("\t").join("');")
      
      // 這時候,只有JavaScript 語句后面才有 "%>", 將 %> 變成 p.push('
      // 上一步我們再 html 標(biāo)簽后加了 ');, 所以要把 p.push(' 語句放在 html 標(biāo)簽放在前面,這樣就可以變成 JavaScript 語句
     .split("%>").join("p.push('")
      // 將上面可能出現(xiàn)的干擾的單引號進(jìn)行轉(zhuǎn)義
       .split("\r").join("\\'")
    // 將數(shù)組 p 變成字符串。
   + "');}return p.join('');");
  return data ? fn( data ) : fn;
 };
})();

上面代碼中,有一個要指出的就是new Function 的使用 方法。給 new Function() 傳一個字符串作為函數(shù)的body來構(gòu)造一個 JavaScript函數(shù)。編程中并不經(jīng)常用到,但有時候應(yīng)該是很有用的。

下面是 new Function 的基本用法:

// 最后一個參數(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ù) 在我們的模板里面是沒有用到的。

要指出的是,采用 push 的方法在 IE6-8 的瀏覽器下會比 += 的形式快,但是在現(xiàn)在的瀏覽器里面, += 是拼接字符串最快的方法。實(shí)測表明現(xiàn)代瀏覽器使用 += 會比數(shù)組 push 方法快,而在 v8 引擎中,使用 += 方式比數(shù)組拼接快 4.7 倍。所以 目前有些更高級的模板引擎會 根據(jù) javascript 引擎特性采用了兩種不同的字符串拼接方式。

下面的代碼是摘自騰訊的 artTemplate 的, 根據(jù)瀏覽器的類型來選擇不同的拼接方式。功能越強(qiáng)大,所考慮的問題也會更多。

var isNewEngine = ''.trim;// '__proto__' in {}
var replaces = isNewEngine
? ["$out='';", "$out+=", ";", "$out"]
: ["$out=[];", "$out.push(", ");", "$out.join('')"];

挑戰(zhàn):有興趣的可以改用 += 來實(shí)現(xiàn)上面的代碼。

總結(jié)

模板引擎原理總結(jié)起來就是:先獲取html中對應(yīng)的id下得innerHTML,利用開始標(biāo)簽和關(guān)閉標(biāo)簽進(jìn)行字符串切分,其實(shí)是將模板劃分成兩部份內(nèi)容,一部分是html部分,一部分是邏輯部分,通過區(qū)別一些特殊符號比如each、if等來將字符串拼接成函數(shù)式的字符串,將兩部分各自經(jīng)過處理后,再次拼接到一起,最后將拼接好的字符串采用new Function()的方式轉(zhuǎn)化成所需要的函數(shù)。

目前模板引擎的種類繁多,功能也越來越強(qiáng)大,不同模板間實(shí)現(xiàn)原理大同小異,各有優(yōu)缺,請按需選擇。

參考文章:

1、Quick Tip: Create a Makeshift JavaScript Templating Solution

2、JavaScript模板引擎的應(yīng)用場景及實(shí)現(xiàn)原理

3、JavaScript構(gòu)建自己的模板小引擎

更多關(guān)于JavaScript相關(guān)內(nèi)容可查看本站專題:《javascript面向?qū)ο笕腴T教程》、《JavaScript切換特效與技巧總結(jié)》、《JavaScript查找算法技巧總結(jié)》、《JavaScript錯誤與調(diào)試技巧總結(jié)》、《JavaScript數(shù)據(jù)結(jié)構(gòu)與算法技巧總結(jié)》、《JavaScript遍歷算法與技巧總結(jié)》及《JavaScript數(shù)學(xué)運(yùn)算用法總結(jié)》

希望本文所述對大家JavaScript程序設(shè)計(jì)有所幫助。

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI