模板的發明是編程史上的一大里程碑,讓咱們擺脫了煩鎖且易出錯的字符串拼接,維護性大大提升。javascript
都在JSP,ASP時代,人們已經學會使用include等語句,將多個頁面片段拼接成一個頁面。 此外,爲了將數據庫中的數據或業務中用到的變量輸出到頁面,咱們須要將頁面某個地方標記一下,將變量塞到裏面去。 最後,出於方便循環輸出一組數據,就須要將each語句從HTML裏撕開一道口子,加上其餘什麼if語句,頁面上其實變撕裂成兩部分 一種是與後端語言相近的邏輯部分,一個是夠爲純淨的HTML部分,到最後,模板引擎就發展出來。html
在jQuery王朝的後期,業務邏輯不斷往前搬,前端模板也發明出來了。這些模板我統稱爲靜態模板或字符串模板,特徵是模板是放在 一個script標籤或textarea標籤裏。靜態模板的好處是統一管理,咱們從script標籤等抽取內容時,它是原汁原味,沒有被竄改。 缺點是破壞原有的結構。MVVM時代,knockout, ember等率先發明動態模板,或叫DOM模塊,特色是經過在元素節點上標記一些特殊屬性,註明此元素裏面會輸出什麼內容 或此元素的子元素是做用循環體要循環多少次,固然if等輸出不輸出很小兒科。缺點是,須要對文檔的總體或某一區域進行掃描,這裏耗時比靜態模板多上幾倍,而且定界符(用於輸出變量的標記)可能離奇失蹤。但這也沒什麼大不了,如今流行的兩種定界符形式≈lt&;, %>,{{ }}在IE10+或W3C瀏覽器活得好好的,IE6-9,咱們只要避開大於小於號就好了。 此外動態模板與靜態模板最大的不一樣在於,它是沒有編譯函數,而是經過掃描文檔,根據節點上的定界符與綁定屬性實現循環輸出,填空等功能。前端
咱們看一下avalon是怎麼作的。大體分兩塊,定義VM,添加綁定。VM是咱們操做的主體,綁定是將頁面變成模板的關鍵。java
VM的定義:git
avalon.define("test",function(vm){ vm.aaa = "司徒正美" })
avalon.define是用來定義VM,第一個參數爲VM的ID名,這是用於在頁面圈定做用域的範圍,對應的綁定屬性是ms-controller。由於一個頁面可能有多人負責,就存在多個VM了,而VM至關於一個數據據,它們都用於不一樣的區域,這裏就須要用ID來區分了。github
添加綁定,咱們隨便往body一塞就好了數據庫
<body>{{aaa}}</body>
這裏的{{ }}是定界符,放在文本節點裏。咱們能夠用過avalon.config({interpolate: ["{?", "?}"]})來設置定界符。編程
<!DOCTYPE html> <html> <head> <title>avalon</title> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width"> <script src="http://files.cnblogs.com/rubylouvre/avalon20130929.js"> </script> <script> avalon.define("test", function(vm){ vm.aaa = "司徒正美" }) </script> </head> <body ms-controller="test"> <h3>{{aaa}}</h3> {{aaa}} </body> </html>
後端
但世界上沒有這麼簡單的頁面,好比咱們要輸出一個列表,是否是要這樣幹呢?數組
<!DOCTYPE html> <html> <head> <title>avalon</title> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width"> <script src="http://files.cnblogs.com/rubylouvre/avalon20130929.js"></script> <script> avalon.define("test", function(vm) { vm.a = "2014預言:阿里死磕港交所" vm.b = "馬雲進軍遊戲背後:恐失勢電商" vm.c = "支付寶信息泄露揭大公司管理困境" vm.d = "盤點2013:智能手機開啓的新場景" }) </script> </head> <body ms-controller="test"> <ol> <li>{{a}}</li> <li>{{b}}</li> <li>{{c}}</li> <li>{{d}}</li> </ol> </body> </html>
固然不行,這要定義多少個變量啊!這時就需用到循環綁定,ms-repeat!
<!DOCTYPE html> <html> <head> <title>avalon</title> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width"> <script src="http://files.cnblogs.com/rubylouvre/avalon20130929.js"></script> <script> avalon.define("test", function(vm) { vm.array = ["2014預言:阿里死磕港交所", "馬雲進軍遊戲背後:恐失勢電商", "支付寶信息泄露揭大公司管理困境", "盤點2013:智能手機開啓的新場景"] }) </script> </head> <body ms-controller="test"> <ol> <li ms-repeat="array">{{el}}</li> </ol> </body> </html>
ms-repeat至關於ms-each-el,後面的-el是可配置可省略。好比改爲ms-repeat-elem,那麼對應的位置要改爲{{elem}}。
咱們還能夠輸出2維數組
<!DOCTYPE html> <html> <head> <title>avalon</title> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width"> <script src="http://files.cnblogs.com/rubylouvre/avalon20130929.js"></script> <script> avalon.define("test", function(vm) { vm.array = [[1, 2], [4, 5], [7, 8]] }) </script> </head> <body ms-controller="test"> <table width="80%" border="1"> <tr ms-repeat-item="array"> <td ms-repeat="item">{{el}}</td> </tr> </table> </body> </html>
輸出對象數組
<!DOCTYPE html> <html> <head> <title>avalon</title> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width"> <script src="http://files.cnblogs.com/rubylouvre/avalon20130929.js"></script> <script> var model = avalon.define("test", function(vm) { vm.array = [{name: 111}, {name: 222}, {name: 333}] }) </script> </head> <body ms-controller="test"> <ul> <li ms-repeat-me="array">{{me.name}}</li> </ul> </body> </html>
如今你們算是對ms-repeat算是有一個大致的瞭解吧。那麼咱們學一點高級的。avalon.define會返回一個VM對象,咱們經過操做它就能實現頁面的操做數據即操做DOM!!!,好比vmodel.array.push。另,咱們想輸出每一個元素對應的索引值,可使用$index這個變量。
<!DOCTYPE html> <html> <head> <title>avalon</title> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width"> <script src="http://files.cnblogs.com/rubylouvre/avalon20130929.js"></script> <script> var model = avalon.define("test", function(vm) { vm.array = [{name: 111}, {name: 222}, {name: 333}] }) var array = model.array setInterval(function(){ array.push({name: Math.random().toString(32).substr(4,14)}) if(array.length > 10){ array.shift() } },500) </script> </head> <body ms-controller="test"> <ul> <li ms-repeat="array">{{$index}}--{{el.name}}</li> </ul> </body> </html>
之因此 能有這樣神奇的效果,是由於avalon會將VM中的數組轉換爲監控數組,它擁有如下方法:
push, shift, unshift, pop, slice, splice, remove, removeAt, removeAll, clear, ensure, sort, reverse, set
如今咱們跳前一步,學一下ms-on-*綁定,實現一個更復雜的效果。ms-on-*的*對應一個事件名,屬性值爲VM中一個函數名,與元素onkeypress, onclick同樣,它的第一個參數默認是事件對象,this指向元素節點,不一樣的是咱們已經對IE6-8下的事件對象作了兼容處理。
<!DOCTYPE HTML> <html> <head> <title>ms-repeat</title> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <script src="http://files.cnblogs.com/rubylouvre/avalon20130929.js"></script> <script> avalon.define("test", function(vm) { vm.array = ["1", "2", "3", "4"] "push,unshift,remove,ensure".replace(/\w+/g, function(method) { vm[method] = function(e) { if (this.value && e.which == 13) {//this爲input元素 vm.array[method](this.value); this.value = ""; } } }) vm.removeAt = function(e) { if (isFinite(this.value) && e.which == 13) {//this爲input元素 var a = ~~this.value vm.array.removeAt(a) this.value = ""; } } "pop,shift,sort,reverse".replace(/\w+/g, function(method) { vm[method] = function(e) { vm.array[method](); } }) }); </script> </head> <body ms-controller="test"> <p>監控數組擁有如下方法,咱們能夠操做它們就能同步對應的區域</p> <blockquote> push, shift, unshift, pop, slice, splice, remove, removeAt, removeAll, clear, ensure, sort, reverse, set </blockquote> <ul> <li ms-repeat="array">數組的第{{$index+1}}個元素爲{{el}}</li> </ul> <p>對數組進行push操做,並回車<input ms-on-keypress="push"></p> <p>對數組進行unshift操做,並回車<input ms-on-keypress="unshift"></p> <p>對數組進行ensure操做,並回車<input ms-on-keypress="ensure"><br/> (只有數組不存在此元素才push進去)</p> <p>對數組進行remove操做,並回車<input ms-on-keypress="remove"></p> <p>對數組進行removeAt操做,並回車<input ms-on-keypress="removeAt"></p> <p><button type="button" ms-on-click="sort">對數組進行sort操做</button></p> <p><button type="button" ms-on-click="reverse">對數組進行reverse操做</button></p> <p><button type="button" ms-on-click="shift">對數組進行shift操做</button></p> <p><button type="button" ms-on-click="pop">對數組進行pop操做</button></p> <p>當前數組的長度爲<span style="color:red">{{array.size()}}</span>,注意 咱們沒法修改數組length的固有行爲,所以它沒法同步視圖,須要用size方法。</p> </body> </html>
有了批量輸出的ms-repeat及經過調用監控數組的方法就能實現對應節點的刪除添加排序,那麼實現grid簡直易如反掌。
<!DOCTYPE HTML> <html> <head> <title>ms-repeat</title> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <script src="http://files.cnblogs.com/rubylouvre/avalon20130929.js"></script> <script> if (!Date.now) { Date.now = function() { return new Date - 0; } } avalon.define('test', function(scope) { scope.selected = "name" scope.options = ["name", "size", "date"] scope.trend = 1 scope.data = [ {name: "aaa", size: 213, date: Date.now() + 20}, {name: "bbb", size: 4576, date: new Date - 4}, {name: "ccc", size: 563, date: new Date - 7}, {name: "eee", size: 3713, date: 9 + Date.now()}, {name: "555", size: 389, date: Date.now() - 20} ]; scope.$watch("selected", function(v) { var t = parseFloat(scope.trend) scope.data.sort(function(a, b) { var ret = a[v] > b[v] ? 1 : -1 return t * ret }) }) scope.$watch("trend", function(t) { var v = scope.selected, t = parseFloat(t) scope.data.sort(function(a, b) { var ret = a[v] > b[v] ? 1 : -1 return t * ret }) }) }); </script> </head> <body ms-controller="test"> <p> <select ms-duplex="selected"> <option ms-repeat="options">{{el}}</option> </select> <select ms-duplex="trend"> <option value="1">up</option> <option value="-1">down</option> </select> </p> <table width="500px" border="1"> <tbody > <tr ms-repeat="data"> <td>{{el.name}}</td> <td>{{el.size}}</td> <td>{{el.date}}</td> </tr> </tbody> </table> </body> </html>
這裏用到了ms-duplex, $watch,你們能夠到《入門教程》看看,都是很簡單的東西。
接着咱們再看看如何循環輸出對象吧,它也是用ms-repeat,不過裏面的變量爲$key, $val。不用多言,看例子。
<!DOCTYPE html> <html> <head> <title>avalon</title> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width"> <script src="http://files.cnblogs.com/rubylouvre/avalon20130929.js"></script> <script> var model = avalon.define("test", function(vm) { vm.object = { grape: "葡萄", coconut: "椰子", pitaya: "火龍果", orange: "橙子" } }) </script> </head> <body ms-controller="test"> <ul> <li ms-repeat="object">{{$key}}--{{$val}}</li> </ul> </body> </html>
好了,循環輸出就到這裏。咱們最後看一下如何實現其餘模板引擎的if語句。它的名字爲ms-if,若是值爲真就輸出,不然不輸出。
<!DOCTYPE html> <html> <head> <title>avalon</title> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width"> <script src="http://files.cnblogs.com/rubylouvre/avalon20130929.js"></script> <script> var model = avalon.define("test", function(vm) { vm.toggle = true vm.click = function(){ vm.toggle = !vm.toggle } vm.text = "捉迷藏" }) </script> </head> <body ms-controller="test"> <p ms-if="toggle">{{text}} </p> <button type="button" ms-on-click="click">點我{{toggle ? '隱藏' : "顯示"}}</button> </body> </html>
{{}}, ms-repeat, ms-if這就是動態模板相對靜態模板的全部功能了,但因爲動態模板在掃描以後,獲得全部要處理的節點的引用,這也意味着,之後咱們要作一小部分的更新,不用像靜態模板那樣大規模替換,而是細化到每個元素節點,特性節點或文本節點。這就是所謂的「最小化刷新」技術。通常的,只有ms-if等少許綁定才影響到元素節點那一層面,更多的時候, 咱們是在刷新特性節點的value值,文本節點的data值,這也意味着,咱們的刷新不會引發reflow。加之,能獲得元素節點本上,咱們就能夠輕鬆實現綁定事件,操做樣式,修改屬性等功能。這也是爲何大多數MVVM框架選擇動態模板的緣故,jQuery原來能夠作的,咱們所有經過綁定屬性或定界符在HTML裏搞定。 這也意味着,咱們實現了完美的分層架構,JS裏面是純粹的模型層(包括model與viewmodel),HTML裏是學習成本與維護成本極低的視圖層。這已經不是多了一個模板引擎這麼簡單的事,咱們搶到了 一直以來屬性於後端的禁臠——分層架構!