• <noscript id="eom2a"><optgroup id="eom2a"></optgroup></noscript>
    <tt id="eom2a"><small id="eom2a"></small></tt>
    <input id="eom2a"></input>
  • <div id="eom2a"><small id="eom2a"></small></div>
    <td id="eom2a"><small id="eom2a"></small></td>
  • 您的位置:知識庫 ? Web前端

    模板,從服務端到客戶端

    作者: Lars Kappert  來源: 伯樂在線  發布時間: 2014-06-24 09:45  閱讀: 5356 次  推薦: 15   原文鏈接   [收藏]  

      英文原文 Client-Side Templating

      在瀏覽器中使用模板是一個日漸熱門的趨勢。將服務端的邏輯應用到客戶端上,還有越來越多的類MVC模式(模型-視圖-控制器:model-view-controller)的使用都使得在瀏覽器中“模板”的角色越來越重要。在過去,“模板”從來都是服務端的事情,但事實上在客戶端開發中,模板的作用是非常強大又具有表現力的。

      為什么要使用模板?

      大體上來說,借助模板是一種能很好地將視圖(views)中標記和邏輯分開的方法,還能將代碼的重用性和可維護性最大化。如果使用的是語法與最終所得結果很相近的語言(比如HTML),你就能又快又好地把任務完成了。雖然模板可以用來輸出任何形式的文本,但由于我們想要討論的客戶端開發是有關于HTML的,所以在這篇文章里,我們還是以HTML作為例子。

      現在的動態應用中,客戶端常常需要頻繁地刷新界面。這個效果可以通過服務端將HTML片段插入到客戶端的文檔中。這樣做的話,服務器要能支持傳送HTML的片段(與之相對:傳送完整的頁面)。還有就是,作為一個要處理這些標記片段的客戶端的開發者,你應該會想能完全控制你的模板。而模板引擎(Smarty)、流量(Velocity)還有ASP這些服務器端的內容你都不用了解,也不用管那些“面條式代碼”(spaghetti code):例如在HTML文檔里是不是出現的臭名昭著的<?或者<%。

      那么現在來看看客戶端模板吧。

      第一印象

      對初學者而言,理解“模板”的含義很重要,foldoc(免費在線計算機詞典)中的解釋是:模板是一種文檔,不過文檔中有形參,再通過模板處理系統的特定語法用實參代替形參。

      讓我們來看看最基本的模板長什么樣子:

    <h1>{{title}}</h1>
    
     <ul>
    
         {{#names}}
    
             <li>{{name}}</li>
    
         {{/names}}
    
     </ul>

      如果你寫過HTML,那么你一定很熟悉上面的代碼。上文的HTML中有一些占位符。這些占位符將會被真實的數據取代。例如這個對象:

     var data = {
    
         "title": "Story",
    
         "names": [
    
             {"name": "Tarzan"},
    
             {"name": "Jane"}
    
         ]
    
     }

      把數據和模板結合起來,就會得到下面的HTML代碼:

     <h1>Story</h1>
    
     <ul>
    
         <li>Tarzan</li>
    
         <li>Jane</ul>
    
     </ul>

      將模板和數據分離開來對于維護HTML來說是一件好事。比如說,如果想要更改標簽或者添加類(class)就只需要更改模板就可以了。另外,對于需要迭代出現的元素(比如<li>),程序員只需要寫一次就好了。

      模板引擎

      模板的語法是根據你需要的模板引擎來決定的(例如:占位符{{title}})。引擎是負責分析模板,用提供的數據替換占位符(變量、函數、循環等等)。

      有些模板引擎看起來沒有什么邏輯性。這指的不是在模板中只能插入簡單的占位符,而是說智能標簽(intelligent tags)方面的特性很少(比如數組迭代器,條件渲染等等)。有些引擎就有很多特性和很好的可擴展性。關于這一點就不在這展開講了,你需要問問自己,在模板中你是否需要、需要多少邏輯。

      每個模板引擎都有自己的API,不過通常你都能找到像render()和compile()這樣的方法。渲染的過程就是將真正的數據放入模板然后呈現出來。也就是說,渲染就是用真正的數據替代了占位符。如果在此期間木板上有什么邏輯,就會被執行。編譯模板指的是解析模板,然后將它轉換成一個JavaScript函數。模板中的邏輯都會被解釋為純JS(plain JavaScript),給定的數據會被傳入這些JS函數中,這么做可以最大程度地優化HTML。

      Mustache實例

      上文中的例子可以借助模板引擎實現,例如使用了Mustache模板語法的mustache.js。關于這種語法更多信息,我會在后面告訴你的。現在先來看看下面的JS代碼能得到什么效果:

    var template = '<h1>{{title}}</h1><ul>{{#names}}<li>{{name}}</li>{{/names}}</ul>';
    
    var data = {"title": "Story", "names": [{"name": "Tarzan"}, {"name": "Jane"}]};
    
    var result = Mustache.render(template, data);

      現在我們需要在頁面上顯示模板,你需要寫這么一行代碼:

    document.body.innerHTML = result;

      第一個客戶端模板就完成了!在代碼文件中加入下面這句,你就可以試一試上面的例子了,或者看下在線演示

    <script src="https://raw.github.com/janl/mustache.js/master/mustache.js"></script>

      組織模板

      如果你和我一樣,不喜歡HTML文檔里出現很長的內容,既造成了閱讀的困難還增加了維護的負擔。理想情況下,我們可以把模板分開維護,既能享受模板的語法高亮的便利,又能保證HTML的可讀性。

      但事情總不會十全十美的。如果一個項目中要使用非常多的模板,出于避免過多Ajax請求而影響性能的原因,我們不希望這么多文件被分開加載下來。

      場景1:腳本標簽

      常見的解決方案就是把所有的模板直接放在<scrpit>標簽中,<script>標簽的可選類型要稍作更改,比如改成type=”type/template”(瀏覽器在渲染或解析時會將這個屬性忽略)。

    <script id="myTemplate" type="text/x-handlebars-template">
    
         <h1>{{title}}</h1>
    
         <ul>
    
             {{#names}}
    
                 <li>{{name}}</li>
    
             {{/names}}
    
         </ul>
    
     </script>

      這樣的做,你就可以把所有的模板都放在HTML文檔中,避免了額外的Ajax請求。

      script標簽中的內容會后面被JavaScript當做模板來使用。請看下面的代碼,這次我們用的是Handlebars模板引擎再結合一些jQuery,模板就用剛剛的里的。也可以直接看在線演示

    var template = $('#myTemplate').html();
    
    var compiledTemplate = Handlebars.compile(template);
    
    var result = compiledTemplate(data);

      最終效果和上文的Mustache例子是一樣的。Handlebars也可以使用Mustache格式的模板,所以在這里我們就用一樣的模板了。不過要注意,它們之間還是有一個很重要的區別:Handlebars是先得到一個中間結果,再通過這個中間值得到HTML的。它先是將模板編譯成一個JS函數(稱之為compiledTemplate),然后數據再被傳入這個函數中執行,再返回最終結果。

      場景2:預編譯模板

      雖然說將渲染模板包裝在一個方法里看起來要方便多了,但是將編譯和渲染分開也有顯而易見的優點。最重要的是,分開以后,可以把編譯放在服務器端完成。我們可以在服務器上執行JS代碼(比如使用Node),有些模板引擎支持這樣的預編譯。

      我們可以用一個JS文檔(叫它comiled.js吧)將多個預編譯好的文件放在一起。這個文件的內容看起來可能是這樣的:

     var myTemplates = {
    
         templateA: function() { ….},
    
         templateB: function() { ….};
    
         templateC: function() { ….};
    
     };

      然后在應用中,我們只需要將數據傳入這些預編譯好的模板中:

    var result = myTemplates.templateB(data);

      這個方法遠比上文中討論過的將所有的模板放在<script type=”text/javascript”>中要好,客戶端會忽略編譯過程。取決于你的應用套件(application stack),這個解決方式并不一定很難實現,我們會在下文看到它具體的實現。

      Node.js示例

      任何模板預編譯腳本至少要滿足下面的要求:

    1. 讀取模板文件,
    2. 編譯模板,
    3. 最后的結果可以被合并入一個或多個文件、

      下文中的Node.js腳本就實現了上面說的那3點(使用Hogan.js模板引擎):

     var fs = require('fs'),
    
         hogan = require('hogan.js');
    
     var templateDir = './templates/',
    
         template,
    
         templateKey,
    
         result = 'var myTemplates = {};';
    
     fs.readdirSync(templateDir).forEach(function(templateFile) {
    
         template = fs.readFileSync(templateDir + templateFile, 'utf8');
    
         templateKey = templateFile.substr(0, templateFile.lastIndexOf('.'));
    
         result += 'myTemplates["'+templateKey+'"] = ';
    
         result += 'new Hogan.Template(' + hogan.compile(template, {asString: true}) + ');'
    
     });
    
     fs.writeFile('compiled.js', result, 'utf8');

      這段代碼先是讀取了在templates目錄下所有的文件,再編譯了這些模板,最后將它們寫入compiled.js。

      注意!現在得到的結果是完全沒有優化過的代碼,也沒有做任何錯誤處理。不過它還是完成我們想要它做的事,也不需要很長的代碼來預編譯模板。

      場景3:AMD和RequireJS

      隨著異步牽引模塊(通常我們都稱之為AMD)越來越多地被使用,為了更好地組織你的APP,建議將模塊解耦。RequireJS是現在主流的模塊加載器之一,在模塊定義中,你可以特定某些依賴,在實際的模塊里你就可以使用它們了(工廠模式)。

      在使用模塊時,RequireJS有一個text插件用于規定基于文本的依賴。默認是將AMD的依賴當做JavaScript來處理,不過模板并不是JS而是文本(比如HTML格式的模板),所以我們需要用上這個插件:

     define(['handlebars', 'text!templates/myTemplate.html'], function(Handlebars, template) {
    
         var myModule = {
    
             render: function() {
    
                 var data = {"title": "Story", "names": [{"name": "Tarzan"}, {"name": "Jane"}]};
    
                 var compiledTemplate = Handlebars.compile(template);
    
                 return compiledTemplate(data);
    
             }
    
         };
    
         return myModule;
    
     });

      這樣,就能在單獨的文件中管理各個模板了,雖然這么做是挺好的,但無疑增加了很多額外的Ajax請求,而且仍然需要在客戶端編譯模板。但是,可以用RequireJS中的r.js來優化這些額外的請求。這個決定了依賴,將模板或者依賴植入模塊定義中,大大減小了請求數。

      你會發現我們還沒有說到預處理,事實上有兩個方法可以完成預處理。可以寫一個r.js的插件或者別的程序來預處理模板。這么做的話就會改動了模塊定義:我們需要在優化之前先使用一個模板*字符串*,然后再使用一個模板*方法*。不過這些問題也不是很難處理,你可以去檢測它的變量類型或者將邏輯抽象出來(寫在插件中或者直接寫在應用中)。

      監聽模板

      在場景2和場景3中,如果將模板當做未編譯的資源我們還能將應用構建地更好。就像你在寫CoffeeScript、Less或者SCSS,在開發時,可以監聽模板文件的變化,一旦發現文件出現變化,就立刻自動重新編譯,就像從CoffeeScript編譯到JavaScript一樣。這樣我們在代碼中處理的模板都是已經預編譯過了的,還方便了在開發過程匯中將預編譯模板做相關的內聯優化。

    define(['templates/myTemplate.js'], function(compiledTemplate) {
    
         var myModule = {
    
             render: function() {
    
                 var data = {"title": "Story", "names": [{"name": "Tarzan"}, {"name": "Jane"}]};
    
                 return compiledTemplate(data);
    
             };
    
         };
    
         return myModule;
    
     }

      性能問題

      用客戶端模板完成UI更新時的渲染是常見的方法。還是那句話,想要達到性能最優,那就要在第一次請求頁面時盡可能少的請求額外的資源。這樣瀏覽器在渲染HTML頁面時不會因為要去加載JS資源或者別的數據而中斷渲染。這聽起來挺難的,特別是在又要動態加載內容又要盡可能減少加載時間的頁面上。理想情況下,模板是既可以在客戶端也可以在服務端使用的,這樣可以提供最優的性能還能保持它的可維護性。

      有兩個問題還需要考慮一下:

    1. 我的應用中哪里是有最多動態加載的呢?又是哪部分需要最短的加載時間的呢?
    2. 處理種種問題的程序是要放在客戶端還是服務端呢?

      實際問題實際分析。確實使用預處理過的模板,客戶端可以比較輕易地快速渲染出效果。但是如果你需要重用模板,你會偏愛邏輯較少的模板一些。

      結論

      我們已經看到了客戶端模板的種種好處,比如:

    • 服務器和API最好只負責提供數據(比如JSON);客戶端模板就能直接把數據套上了。
    • 客戶端方向的開發者可以自如地使用HTML和JS。
    • 使用模板的話,你就必須把邏輯和表現分離開。
    • 模板可以預編譯好然后緩存起來,這樣服務器每次都只要發送數據就可以了。
    • 不在服務器端渲染而在客戶端渲染,多少會影響性能。

      上述的文字已經介紹了很多關于(客戶端)模板的知識,希望現在你對這些內容有了更深的認識。

    15
    1

    Web前端熱門文章

      Web前端最新文章

        最新新聞

          熱門新聞

            黄色网_免费在线黄色电影_黄色成人快播电影_伦理电影_黄色片