avalon通過幾年之後,已成爲國內一個舉足輕重的框架。它提供了多種不一樣的版本,知足不一樣人羣的須要。好比avalon.js支持IE6等老舊瀏覽器,讓許多靠政府項目或對兼容性要求夠高的公司也能享受MVVM的樂趣。avalon.modern.js支持IE10以上版本,優先使用新API,性能更優,體積更少。avalon.mobile.js在avalon.modern的基礎提供了觸屏事件的支持,知足你們在移動開發的需求。此外,它們分別存在avalon.xxx.shim版本,指無自帶加載器版,avalon.xxx.min版本,指上線壓縮版本。javascript
avalon早期嚴重受到angular與knockout的影響,API與它們很相近,通過多年的發展,漸漸摸索出本身一套模式。avalon1.5是一個里程碑的版本,它帶來許多全新的特性,讓咱們編寫代碼更加爽快。php
avalon1.5的下載地址: https://github.com/RubyLouvre/avalon/tree/1.5css
avalon與jQuery最大的一個區別是,思惟的不一樣。jQuery要操做一個元素,老是設法找到此元素,想象這個元素是否有ID,有某個類名,存在某個特定的標籤下,是父節點的第幾個孩子,諸如此類,最後拼湊出一個CSS表達式,而後$(expr)找到元素,而後再進行操做,因而JS代碼裏滿屏$。維護代碼的人,老是要對着頁面來看看,這表達式是對應某某元素,若是隻有ID,類名還好,新手非常寫出很長的CSS表達式,致使你最後崩潰掉。html
avalon要操做某個元素,就直接在HTML爲它添加一些指令,這些指令或者以ms-開頭的元素屬性,或是標籤之間的4個花括號。指令裏面存在某些變量,這些變量最後在JS彙集成一個對象,這就叫作VM( View Model, 視圖模型 )。咱們只要操做這個VM的數據變更就好了,頁面上就會自動變化。有了這一層的分離,咱們在代碼量就少能許多操做DOM的代碼,專致於業務自己。好比說:java
<p>{{aaa}}</p>
至關於jQuery的如下代碼:node
$(function(){
$("p").text(aaa) })
那咱們看看怎麼定義一個VM吧。avalon在1.5以前存在兩種定義方式,如今1.5只支持新風格,即git
var vm = avalon.define({
$id: "test", a: 1, b: 2, c: { d: 1 }, onClick: function(e){ e.preventDefault() }, arr: [1,2,3] })
avalon.define是一個很是重要的方法,要求傳入一個對象,對象裏面必須有$id屬性,它是用於指定其在頁面的做用範圍。github
avalon.define會返回一個新對象,它除了以前咱們定的屬性與方法,還添加了$watch, $events, $fire, $model等屬性與方法。ajax
當咱們以vm.a = 4來從新賦值時,頁面上用到a的地方會天然做出反應,這個行爲稱之爲 綁定 ,有的屬性會使用ms-duplex指令綁定到表單元素上,這時反應是雙向的,input,select, textarea的值被用戶改動時,會天然反應到VM上,而咱們對VM上的操做也會反應到表單元素上,這叫作 雙工綁定json
有的東西,你壓底只有它只做用一次,如大表格的數據展現,之後沒有任何互動交互,那咱們有幾種方式:
<!DOCTYPE html>
<html> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <script src="avalon.js"></script> <script> var vm = avalon.define({ $id: "test", a: 1, $b: 2, $skipArray: ["a"], c: 3 }) setTimeout(function () { vm.a = 100 vm.$b = 100 vm.c = 100 }, 3000) </script> </head> <body> <div ms-controller="test"> <p>{{a}}不會變</p> <p>{{$b}}不會變</p> <p>{{::c}}不會變</p> <p>{{c}}會變</p> </div> </body> </html>
VM是一個對象,它除了包含一些必須的方法與屬性外,其餘的東西就分爲兩大類,非監控屬性與監控屬性。
非監控屬性,就是咱們上面指的以$開頭,或是名字定義在$skipArray數組的東西,此外,當某屬性的值的類型爲函數或元素節點,文本節點,註釋節點,文檔對象,文檔碎片與window時,它們也沒法監控。
監控屬性則分爲4類:
在1.5以前的版本,還有一個叫監控函數的東西,即裏面包含了某些監控方法。但如今咱們不建議這樣用,由於在將來的版本,咱們打算像angular那樣經過純靜態詞法分析,就能獲得此指令所依賴的全部監控屬性。而監控方法則須要使用動態的依賴檢測實現。動態依賴檢測雖然很是強大,但也很是耗性能。在1.5以前,avalon是徹底經過動態依賴檢測實現綁定的,1.5是結合靜態詞法分析與動態依賴檢測,將來會一點點改成純靜態詞法分析。
var vm = avalon.define({
$id: "test", a: 1, $b: 2, $skipArray: ["a"], c: 3, //監控屬性 d: { //這是子VM dd: { ddd: 3 }, dd2: 4 }, arr: [1, 2, 3, 4], //監控數組 $computed: { c: {//計算屬性c get: function () { return this.a + " " + this.c }, set: function (val) { var arr = val.split(" ") this.a = arr[0] this.b = arr[1] } }, e: {//計算屬性e get: function () { return this.a + 100 } } } })
爲了方便協做開發的需求,咱們引入了做用域的概念。由於一個頁面可能很大,分爲N個模塊,每一個模塊交同不一樣的人來編寫。這個在移動端的SPA應用中尤其明顯。 對於JS,咱們能夠拆分爲N個JS文件,每一個JS文件都有本身的VM。頁面也是拆成一塊塊,這能夠經過PHP或nodejs的模板貼合起來。而在這以前,咱們先爲它們加上ms-controller!
ms-controller爲一個指令,其值爲一個VM的$id,如ms-controller="test",它就會在avalon.vmodels中找到該VM,而後這個元素下方用到的全部指令中的變量,都應該位於此VM。
但若是一個功能模塊特別複雜,它用到的字段特別多,意味着這個VM也要定義許多許多屬性,而這些屬性的某一部分也在其餘頁面或模塊用到,這時咱們就須要對它進行拆分,方便重用。拆分後的兩個對象或N個對象,avalon容許咱們以ms-controller套ms-controller的形式,實現做用域間的數據共享。換言之,若是某變量在當前的VM換不到,它就會往上找,在上面的VM中查找此屬性,一直找到爲止。這有點像JS的對象屬性查找,其實,它像CSS的做用域查找,由於咱們還引入了ms-important。ms-important的寓意就是CSS中的important!符號,就在此做用域查找,不往外找!
此外,還有些地方,你不想avalon來處理它們,如script標籤的內容,style標籤的內容,文章的語法高亮部分,引用別人文章的部分,這個可使用ms-skip指令來繞開這些無用的區域。
至少,咱們學習了ms-controller, ms-important, ms-skip, 更詳細能夠到 新官網 上學習
avalon能實現VM與視圖之間的互動,最關鍵的東西就是這個。在有的MVVM框架,這也叫作編譯(compile),意即,將視圖的某一部分的全部指令所有抽取出來,轉換爲一個個視圖刷新函數,而後放到一個個數組中,當VM的屬性變更時,就會執行這些數組的函數。固然數組裏面的東西不定是函數,也多是對象,但裏面確定有個視圖刷新函數。這是MVVM框架的核心機制,但怎麼抽取出來,每一個框架的方式都不同。avalon將這個過程稱之爲掃描。掃描老是從某個節點開始。在avalon內部,已經默認進行了一次掃描,從body元素開始描。若是咱們爲頁面插入了什麼新內容,而這個區域裏面又包括了avalon指令,那麼咱們就須要手動掃描了。
avalon.scan是avalon第二重要的API,它有兩個參數,第一個是元素節點,第二個是數組,裏面爲一個個VM。固然這兩個參數是可選的。但當你手動掃描時,最好都會進去,這樣會加快掃描速度,並減小意外。由於全部指令,都掃描後就變移除掉,這包括指定VM用的ms-controller,ms-important!
<!DOCTYPE html>
<html> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <script src="avalon.js"></script> <script> avalon.ready(function () { var div = document.createElement("div") div.innerHTML = "{{aaa}}" div.setAttribute("ms-controller", "eee") document.body.appendChild(div) var vm = avalon.define({ $id: "eee", aaa: 111 }) avalon.scan(div, vm) }) </script> </head> <body> </body> </html>
指令是指寫在HTML中的特殊符號,包括如下幾種,ms-開頭的綁定屬性,寫在innerText裏面的{{}}的插值表達式,相似data-duplex-xxx的輔助指令(data-後面跟着的綁定屬性的名字,它們必須與綁定元素定義在同一元素),還有新添加的自定義標籤(它們必須帶有:號)
新手們或從angular過來的人很容易犯一個錯誤,就是直接在屬性值裏面加一個{{}},覺得就能綁定了,卻不知avalon爲了性能優化,會跳過全部非ms-*屬性。
這裏擁有 全部指令的一覽圖
這裏提供ms-text, ms-html兩種指令,其餘ms-text擁有{{expr}}這個變體,ms-html擁有{{expr|html}}這個變體。當大家頁面也使用後端模板拼湊而成時,可能 後端會佔用了{{}}界定符,咱們能夠經過如下配置方式從新指定界定符
avalon.config({ interpolate:["{%","%}"] })
而且咱們能夠經過avalon.config.openTag, avalon.config.closeTag獲得「{%」,"%}"。注意,界定符裏面千萬別出現<, >,由於這存在 兼容性問題 。這兩個界定符也不能同樣,最好它們的長度都大於1。
<script> avalon.define({ $id: "test", text: "<b> 1111 </b>" }) </script> <div ms-controller="test"> <div><em>用於測試是否被測除</em>xxxx{{text}}yyyy</div> <div><em>用於測試是否被測除</em>xxxx{{text|html}}yyyy</div> <div ms-text="text"><em>用於測試是否被測除</em>xxxx yyyy</div> <div ms-html="text"><em>用於測試是否被測除</em>xxxx yyyy</div> </div>
插值表達式{{}}在綁定屬性的使用 , 只限那些能返回字符串的綁定屬性 ,如ms-attr、ms-css、ms-include、ms-class、 ms-href、 ms-title、ms-src等。一旦出現插值表達式,說明這個整個東西分紅可變的部分與不可變的部分,{{}}內爲可變的,反之亦然。 若是沒有{{}}說明整個東西都要求值,又如ms-include="'id'",要用兩種引號強制讓它的內部不是一個變量。
ms-include指令是ms-html的有效補充。咱們知道ms-html是將VM中某個符合HTML結構的字符串,放到某元素底下解析爲節點。但若是這個字符串很大,放在VM上就不合算,這時咱們就想到將它到頁面的某個位置上(如script, noscript, textarea等能放大片內容的特殊標籤)或乾脆獨立成一個HTML文件。因而前者叫作內部模板,由於是放在頁面的內部,後者叫作外部模板。對於前者,咱們使用ms-include=「expr」來引用,後者,咱們是使用ms-include-src="expr"來引用。src表示一個路徑,所以其值每每是一個URL地址,爲了你們方便拼接URL,咱們容許ms-include-src的值可使用插值表達式。如ms-include-src="aaa/{{bbb}}.html"。因爲咱們加載外部模板時是用AJAX實現的,所以你們在調試代碼時,必須打開WEB服務器。
<html> <head> <meta http-equiv="content-type" content="text/html; charset=UTF-8" /> <script src="avalon.js"></script> <script> avalon.define({ $id: "test", xxx: "引入內部模板" }) </script> </head> <body > <script type="avalon" id="tpl"> here, {{ 3 + 6 * 5 }} </script> <div ms-controller="test"> <p>{{xxx}}</p> <div ms-include="'tpl'"></div> </div> </body> </html>
注意,ms-include的值要用引號括起,表示這只是一個字符串,這時它就會搜索頁面的具備此ID的節點,取其innerHTML,放進ms-include所在的元素內部。不然這個tpl會被當成一個變量, 框架就會在VM中檢測有沒有此屬性,有就取其值,重複上面的步驟。若是成功,頁面會出現here, 2的字樣。
若是你們想在模板加載後,加工一下模板,可使用data-include-loaded來指定回調的名字。
若是你們想在模板掃描後,隱藏loading什麼的,可使用data-include-rendered來指定回調的名字。
因爲ms-include綁定須要定義在一個元素節點上,它的做用僅僅是一個佔位符,提供一個插入位置的容器。 若是用戶想在插入內容後,去掉這容器,可使用data-include-replace="true"。
avalon在使用ms-include-src 加載外部模板時,會將它們存放到avalon.templateCache對象中,所以咱們能夠搞出一種架構出來,在上線前,將全部要遠程加載的模板所有打包到avalon.templateCache對象中,這樣它在發出請求前,先查找此對象,發現存在就不會發出請求了。
注意,不管是ms-include仍是ms-include-src都會在其值變化時,請空原元素的全部子孫節點,致使原有數據丟失,裏面用到的全部組件從新生成,若是保持原來的節點,可使用data-include-cache="true"輔助指令。
下面是一個經典的後臺系統框架!
<!DOCTYPE html>
<html> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <script src="avalon.js"></script> <script> avalon.templateCache = { aaa: "<div>這裏是很是複雜的HTML結構1</div>", bbb: "<div>這裏是很是複雜的HTML結構2</div>" ccc: "<div>這裏是很是複雜的HTML結構3</div>" ddd: "<div>這裏是很是複雜的HTML結構4</div>" } var vm =avalon.define({ $id:"root", tabs:["aaa","bbb", "ccc","ddd"],//全部標籤頁的名字 curTab: "aaa", switchTab: function(el){ vm.curTab = el }, showLoading: function(){}, hideLoading:function(){} }) </script> </head> <body ms-controller="root"> <table> <tr> <td> <ul> <li ms-repeat="tabs" ms-click="switchTab(el)">{{el}}</li> </ul> </td> <td> <!--主內容顯示區--> <div ms-include-src="curTab" data-include-loaded="showLoading" data-include-rendered="hideLoading" > </div> </td> </tr> </table> </body> </html>
更詳細的內容可見 新官網
avalon1.5如今只支持新風格,即ms-class="aaa: true"這種形式,此綁定屬性的值以冒號分爲兩部分,前面爲類名,後面表示添加或移除。 ms-class="aaa bbb ccc: toggle",當toggle在VM中爲true時,它會爲元素同時添加aaa, bbb, ccc三個類名。冒號及其後面的東西也不是必須的, 如ms-class="aaa1 bbb2",表示老是爲元素添加aaa1,bbb2這兩個類名。前面的部分也可使用插值表達式動態生成,如ms-class="{{className}}:true", className在VM是什麼,就會爲元素添加什麼類名。若是你想爲元素添加多個類名綁定,可使用ms-class-1="aaa: true" ms-class-2="bbb:toggle"來添加。
ms-hover, ms-active與ms-class的用法相同,但它們一個只在鼠標掠過元素上方時添加類名,移走時移除;另外一個則在元素得到焦點時(好比點擊)添加類名,失去焦點時移除。
更詳細的內容可見 新官網
咱們能夠經過ms-on-*爲元素綁定各類事件,好比ms-on-click=fn,表示爲當前元素綁定點擊事件,fn爲VM的一個函數。默認地,咱們會爲fn傳入一個參數event,咱們已經爲它作了兼容處理,所以你在IE下也能使用preventDefault, stopPropagation, pageX, pageY, target, timeStamp, which等標準屬性與方法。若是你還想傳其餘參數,還想用事件對象,能夠用$event佔位。ms-on-click=fn(aaa, bbb, $events)。此外,咱們爲全部經常使用事件作了快捷處理,所以大家還能夠這樣用,ms-click=fn2, ms-mouseover=fn3, ms-mouseleave=fn4。 注意,事件綁定的值的第一個單詞必須是VM中的函數名字,你不能在其值裏面使用加減乘除,如 ms-click="aaa+bbb",這樣是不對的。若是 你想同時綁定多個點擊事件,用法與ms-class,ms-hover同樣,在後面加數字就好了。ms-click-1=fn1 ms-click-2=fn2 ms-click-3=fn3。
<!DOCTYPE HTML>
<html> <head> <title>ms-on</title> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <script src="../avalon.js" ></script> <script> var model = avalon.define({ $id: "test", firstName: "司徒", array: ["aaa", "bbb", "ccc"], argsClick: function(e, a, b) { alert([].slice.call(arguments).join(" ")) }, loopClick: function(a, e) { alert(a + " " + e.type) }, status: "", callback: function(e) { model.status = e.type }, field: "", check: function(e) { model.field = this.value + " " + e.type }, submit: function() { var data = model.$model if (window.JSON) { setTimeout(function() { alert(JSON.stringify(data)) }) } } }) </script> </head> <body> <h3 style="text-align: center">ms-on-*</h3> <fieldset ms-controller="test"> <legend>有關事件回調傳參</legend> <div ms-mouseenter="callback" ms-mouseleave="callback">{{status}}<br/> <input ms-on-input="check"/>{{field}} </div> <div ms-click="argsClick($event, 100, firstName)">點我</div> <div ms-each-el=