迷你MVVM框架 avalonjs 入門教程

  1. 關於AvalonJs
  2. 開始的例子
  3. 掃描
  4. 視圖模型
  5. 數據模型
  6. 綁定屬性與動態模板
  7. 作用域綁定(ms-controller, ms-important)
  8. 模板綁定(ms-include)
  9. 數據填充(ms-text, ms-html)
  10. 類名切換(ms-class, ms-hover, ms-active)
  11. 事件綁定(ms-on,……)
  12. 顯示綁定(ms-visible)
  13. 插入綁定(ms-if)
  14. 雙工綁定(ms-duplex,原來的ms-model)
  15. 樣式綁定(ms-css)
  16. 數據綁定(ms-data)
  17. 布爾屬性綁定(ms-checked, ms-selected, ms-readonly, ms-disabled, ms-enabled)
  18. 字符串屬性綁定(ms-title, ms-src, ms-href……)
  19. 萬能屬性綁定(ms-attr)
  20. 萬能綁定(ms-bind)
  21. 循環綁定(ms-each)
  22. UI綁定(ms-ui)
  23. $watch
  24. 過濾器
  25. AMD加載器

關於AvalonJS

avalon是一個迷你的MVVM框架,雖然從發佈到現在,它臌脹了不少,但它現在還是比knockout小許多。avalon開發過程一直遵循三個原則:1,複雜即錯誤,2,數據結構優於算法,3,出奇制勝。這三大原則保證avalon具有良好的維護性,擴展性,與衆不同。

簡單說一下其他三大MVVM的實現思路:

  1. knockout:最早冒出來的JS MVVM庫,通過轉換VM中所有要監聽的東西爲函數,然後執行它們,得到某一時刻中,一共有多少函數被執行,將它們放到棧中,最底的就是最先被執行的,它上面的就是此函數所依賴的函數,從而得到依賴關係。 然後設計一個觀察者模式,從上面的依賴檢測中,將依賴函數作爲被依賴者(最先執行的那個的)的訂閱者,以後我們對被依賴者進行賦值時,就會通先訂閱者更新自身,從而形成一個雙向綁定鏈。 並且,knockout會將視圖中的綁定屬性進行轉換,分解出求值函數與視圖刷新函數,視圖刷新函數依賴於求值函數,而求值函數亦依賴於我們VM中的某些屬性(這時,它們都轉換爲函數),在第一次掃描時,它們會加入對應屬性的訂閱者列隊中, 從而VM中的某個屬性改變,就會自動刷新視圖。
    評價:實現非常巧妙,是avalon0.1-0.3的重要學習對象,但將屬性變成一個函數,讓人用點不習慣,許多用法都有點笨笨的。 雖然是一個輕盈的庫,但擴展性不強,裏面的實現異常複雜,導致能參與源碼的人太少。
  2. emberjs: 一個大而全的框架,包羅萬象。一開始是使用Object.defineProperty+觀察者實現,但IE8的問題,讓它不得不啓用上帝setter, 上帝getter。沒有自動收集依賴的機制,沒有監控數組,計算屬性需要自己指定依賴。VM可繼承。 VM與視圖的雙向綁定依賴於其強大無比上萬行的Handlebars 模板。聽說是外國目前最好用的MV*框架。因爲作者既是jQuery的核心成員,也是Rails的核心成員,雖然由於技術能力沒實現自動收集依賴,但框架的其他方面做得非常易上手,人性化。 
    評價:太大了,優缺點同python的Django框架。
  3. angular: google組織開發的框架,體現其算法至上的時候到了。裏面一共有兩個parser, 一個是ngSanitize/sanitize.js下的HTML parser, 一個是ng/parse.js(它要配合compile.js使用)的JS parser。第一個parser負責綁定抽取,第二個負責從Ctrl函數,工廠函數,服務函數及$watch回調中分解出無數setter, getter, 確認它們的依賴關係,放進觀察者模式中。它的觀察者無比強大,由於它的VM能繼承,於是通過繼承鏈實現四通發達的消息廣播。它還實現了一個基於LRU的緩存系統,因爲google最喜歡以空間換時間了,另一方面說明它要緩存的東西太多了,非常吃內存。 公司內部用angular實現的grid,200行在PC中就拖不動了。它還用到許多時髦的東東,如HTML5 history API, 迷你版Q Promise。內部是極其複雜。 不過最大的問題是,它是基於parser,靜態編譯,這意思着什麼呢?不抗壓縮!爲此,它引進了IOC,官網上給出的簡單例子其實在項目完全不可用,我們需要使用另一種更復雜的寫法,方便編澤器從它們得到不被壓縮的部分, 讓它在壓縮情況也能正常運行。由於基於編譯,許多行爲都不是即時的,可預見的。用戶寫的那些控制器函數,都是爲編譯做準備。由於基於編譯,它不得不要求我們對具有兼容問題的一些全局函數,方法進行屏蔽,用它的給出的服務替代它們,如 window對應$window, document對應$document, location對應$location, setTimout對應$timeout……如果不遵循這規則,它可能運行不了,你需要手動使用$digest手動觸發。不過,它與emberjs一樣,走大而全的道路,連測試框架也有了,並且由於是MVVM,因此比起其他框架易寫測試。
    評價:上手難度非常大,沒有想象中的好用。有句話是這樣說的,Backbone像是穿救生衣游泳,你要自己游到對岸去,Angular像是開快艇,但是沒有救生衣。

現在的avalon是我在完全消化了knockout發展起來的,準確來說,是0.4版,通過Object.defineProperties與VBScript實現了與普通對象看起來沒什麼兩樣的VM,VM裏面充滿了訪問器屬性,而訪問器屬性肯定對應一個setter,一個getter, 我們就在setter, getter中走knockout的老路,實現自動收集依賴,然後放進一個簡單的觀察者模式中,從而實現雙向綁定。將綁定屬性分解爲求值函數與視圖刷新函數,早前,avalon也與knockout一樣使用一個簡單的parser,然後通過with實現, 0.82一個新的parser 上馬,同樣的迷你,但生成的求值函數,更方便依賴收集,並且沒有with語句,性能更佳。angular也不是一無是處,我也從它那裏抄來了{{}}插值表達式,過濾器機制,控制器綁定什麼的。

avalon在內部使用了許多巧妙的設計,因此能涵蓋angular絕對大多數功能,但體積卻非常少。此外,在性能上,現在除了chrome外,它都比knockout快,angular則是最慢的。 在移動端上,avalon這個優勢會被大大放大化的。

關於avalon的幾點:

  • 兼容IE6
  • 沒有AJAX與動畫模塊,需要配合jQuery等庫使用
  • avalon會自動同步視圖,因此不要在VM中進行DOM操作

迷你MVVM框架在github的倉庫https://github.com/RubyLouvre/avalon, 如果你要兼容IE6,那麼下其中的avalon.js, 如果你只打算兼容IE10與標準瀏覽器,那麼下avalon.mobile.js。

官網地址http://rubylouvre.github.io/mvvm/

大家可以加入QQ羣:79641290進行討論,此羣爲技術羣,禁水!

開始的例子

我們從一個完整的例子開始認識 avalon :

  1. <!DOCTYPE html>  
  2. <html>  
  3.     <head>  
  4.         <title></title>  
  5.         <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">  
  6.         <script src="avalon.js"></script>  
  7.     </head>  
  8.     <body>  
  9.         <div ms-controller="box">  
  10.             <div style=" background: #a9ea00;" ms-css-width="w" ms-css-height="h"  ms-click="click"></div>  
  11.             <p>{{ w }} x {{ h }}</p>  
  12.             <p>W: <input type="text" ms-model="w" data-event="change"/></p>  
  13.             <p>H: <input type="text" ms-model="h" /></p>  
  14.         </div>  
  15.         <script>  
  16.             avalon.define("box", function(vm) {  
  17.                 vm.w = 100;  
  18.                 vm.h = 100;  
  19.                 vm.click = function() {  
  20.                     vm.w = parseFloat(vm.w) + 10;  
  21.                     vm.h = parseFloat(vm.h) + 10;  
  22.                 }  
  23.             })  
  24.         </script>  
  25.     </body>  
  26. </html>  
  27.   
  28.           

上面的代碼中,我們可以看到在JS中,沒有任何一行操作DOM的代碼,也沒有選擇器,非常乾淨。在HTML中, 我們發現就是多了一些以ms-開始的綁定屬性與{{}}插值表達式,有的是用於渲染樣式, 有的是用於綁定事件。在ms-model中,我們會發現它會反過來操作VM,VM的改變也會影響視圖的其他部分。

掃描

不過上面的代碼並不完整,它能工作,是因爲框架默認會在DOMReady時掃描DOM樹,將視圖中的綁定屬性與{{}}插值表達式抽取出來,轉換爲求值函數與視圖刷新函數。

我們可以通過下面方法自己掃描DOM樹:

  1. avalon.ready(function() {  
  2.      avalon.define("box"function(vm) {  
  3.          vm.w = 100;  
  4.          vm.h = 100;  
  5.          vm.click = function() {  
  6.              vm.w = parseInt(vm.w) + 10;  
  7.              vm.h = parseInt(vm.h) + 10;  
  8.          }  
  9.      })  
  10.      avalon.scan()  
  11.  })  

scan有兩個可選參數,第一個是掃描的起點元素,默認是HTML標籤,第2個是VM對象。

  1. //源碼  
  2.     avalon.scan = function(elem, vmodel) {  
  3.         elem = elem || root  
  4.         var vmodels = vmodel ? [].concat(vmodel) : []  
  5.         scanTag(elem, vmodels)  
  6.     }  
  7.           

視圖模型

我們是通過avalon.define函數返回一個視圖對象VM,並且avalon.define(vmName, function(vm){})中的vm並不等於VM,工廠函數中的vm是用於轉換爲VM的。生成的VM比用戶指定的屬性還多了許多屬性。

默認的,除了函數外,其他東西都轉換爲監控屬性,計算屬性與監控數組。如果不想讓它轉換,可以讓此屬性以 $開頭,框架就不會轉換它們。

如果實在不放便改名,又不想被轉換,比如是一個jQuery對象或一個DOM節點,如果轉換,肯定拖死框架,我們可以放到vm.$skipArray = [propName1, propName2]中去,這樣也忽略轉換。

另外,avalon不允許在VM定義之後,再追加新屬性與方法,比如下面的方式是錯誤的:

                var vm = avalon.define("test", function(vm) {
                    vm.test1 = '點擊測試按鈕沒反應 綁定失敗';
                });
                vm.one = function() {
                    vm.test1 = '綁定成功';
                };
   //這裏有兩個錯誤,
   //1在命名上沒有區分avalon.define的返回值與它回調中的參數,
   //2one方法的定義位置不對(這是考慮到兼容IE6-8,要求所有瀏覽器保持行爲一致)
        

數據模型

當我們要用AJAX與後端交互時,如果直接把VM傳上去太大了,這時我們需要把它對應的純數組的JS對象。在VM中有個叫$model的屬性,這是一個對象,就是數據模型M了。當我們更改VM時,框架就會自動同步M

綁定屬性與動態模板

在開始之前,我們看一下靜態模板是怎麼工作的:

我之前寫了一個叫ejs的靜態模板引擎:

  1. <script type="tmpl" id="table_tmpl">  
  2.         <&= title() &>  
  3.         <table border=1>  
  4.         <&- for(var i=0,tl = @trs.length,tr;i<tl;i++){  -&>  
  5.             <&- tr = @trs[i]; -&>  
  6.             <tr>  
  7.             <td><&= tr.name;; &></td> <td><&= tr.age; &></td> <td><&= tr.sex || "男" &></td>  
  8.             </tr>  
  9.             <& } &>  
  10.         </table>  
  11.         <&# 怎麼可能不支持圖片 &>  
  12.         <img src="<&= @href &>">  
  13. </script>  
  14.           

它是以一個script標籤做容器,裏面的整個叫模板。模板裏面有許多以 <& 與 &>劃分出來的區塊,用於插入JS代碼,以@開頭的變量是對應於數據包中的某個屬性。

幾乎所有靜態模板的實現原理都是一樣的,將這個模板變成一個函數,然後裏面分成靜態部分與動態部分,靜態部分就是上面的HTML部分,轉換爲一個個字符串,動態部分就是插入的JS代碼, 它們基本上原封不動地成爲函數體的邏輯。然後我們傳入一個對象給這個函數,最後得到一個符合HTML格式的字符串,最後用它貼到頁面上某個位置就行了。

靜態模板有幾個缺點,首先它容易混入大量的JS邏輯,對於菜鳥來說,他們特別喜歡在裏面放入越來越多JS代碼。這個在JSP年代,已經證明是bad practice。爲此出現了logic-less的 mustache。 其次,它更新視圖總是一大片一大片地處理,改動太大。最後,是由於第2點引發的問題,它對事件綁定等不友好,因爲一更新,原來的節點都被消滅了,需要重新綁定。幸好,jQuery普及了事件代理,這問題纔沒有 暴露出來。

再看動態模板,幾乎所有MVVM框架都用動態模板(當然也有例外,如emberjs)。動態模板以整個DOM樹爲容器,它通過掃描方式進行第一次更新視圖。 在靜態模板,通過<& 與 &>劃分的部分,轉換爲綁定屬性與{{}}插值表達式(這是一種文本綁定,在avalon中,我們可以通過|html過濾器,轉換html綁定) 這樣就有效阻止用戶在頁面上寫邏輯。雖然動態模板也支持ms-if, ms-each等表示邏輯關係的綁定,但它的值最複雜也只能是一個表達式。 在綁定屬性中,屬性名用於指定操作行爲,如切換類名,控制顯示,循環渲染,綁定事件,數據填充什麼的,而屬性值是決定這些操作是否執行,與渲染結果。 由於雙向綁定的關係,它不像靜態模板那樣,每次都要自己將數據包放進函數,得到結果,然後innerHTML刷新某個區域。它是在用戶爲VM的某個屬性進行重新賦值,將視圖中對應的某個文本節點, 特性節點或元素節點的值進行重刷。因此不會影響事件綁定。

在avalon中,這些視圖刷新函數都有個element屬性,保持對應的元素節點,每次同步時,都會檢測此元素節點是否在DOM樹,不在DOM樹就取消訂閱此刷新函數,節約內存,防止無效操作。

因此,你們可以看區別了吧。綁定屬性與插值表達式就是對應靜態模板中的JS邏輯部分,由於只允許爲表達式或單個屬性值,複雜度被控制了,強制用戶將它們轉移到VM中。 VM作爲一個數據源,對應靜態模板的數據包,並且多了一個自動觸發功能,進化成一個消息中心。

  1. <p ms-controller="test" ms-click="click">{{ a }}</p>  
  2.   
  3. <script>  
  4.     avalon.define("test", function(vm) {  
  5.         vm.a = '123';  
  6.         vm.click = function() {  
  7.             vm.a = new Date - 0  
  8.         }  
  9.     })  
  10. </script>  

最後要注意的是,HTML5已經規定,特性節點的名字只能小寫,因此什麼ms-ui-xxx, 都要小寫化!這是瀏覽器行爲,無可奈何。

作用域綁定(ms-controller, ms-important)

avalon提供ms-controller, ms-important來指定VM在視圖的作用範圍。比如有兩個VM,它們都有一個firstName屬性,在DIV中,如果我們用 ms-controller="VM1", 那麼對於DIV裏面的{{firstName}}就會解析成VM1的firstName中的值。

有關它們的詳細用法,可見這裏

模板綁定(ms-include)

如果單是把DOM樹作爲一個模板遠遠不夠的,比如有幾個地方,需要重複利用一套HTML結構,這就要用到內部模板或外部模板了。

內部模板是,這個模板與目標節點是位於同一個DOM樹中。我們用一個MIME不明的script保存它,然後通過ms-include="id"引用它。

  1. <script type="text/avalon" id="tpl">  
  2.     here, {{ 1 + 1 }}  
  3. </script>  
  4.   
  5. <div  ms-include="'tpl'"></div>  

注意,ms-include的值要用引號括起,表示這只是一個字符串,這時它就會搜索頁面的具有此ID的節點,取其innerHTML,放進ms-include所在的元素內部。否則這個tpl會被當成一個變量, 框架就會在VM中檢測有沒有此屬性,有就取其值,重複上面的步驟。如果成功,頁面會出現here, 2的字樣。

外部模板,通常用於多個頁面的複用,因此需要整成一個獨立的文件。這時我們就需要通過ms-include-src="src"進行加載。

比如有一個HTML文件tmpl.html,它的內容爲:

  1. <div>這是一個獨立的頁面</div>  
  2. <div>它是通過AJAX的GET請求加載下來的</div>  
  3.      

然後我們這樣引入它

  1. <div  ms-include-src="'tmpl.html'"></div>  

數據填充(ms-text, ms-html)

這分兩種:文本綁定與HTML綁定,每種都有兩個實現方式

  1. <script>  
  2.      
  3.  avalon.define("test", function(vm) {  
  4.       vm.text = "<b> 1111  </b>"  
  5.  })  
  6.   
  7. </script>  
  8. <div ms-controller="test">  
  9.     <div><em>用於測試是否被測除</em>xxxx{{text}}yyyy</div>  
  10.     <div><em>用於測試是否被測除</em>xxxx{{text|html}}yyyy</div>  
  11.     <div ms-text="text"><em>用於測試是否被測除</em>xxxx yyyy</div>  
  12.     <div ms-html="text"><em>用於測試是否被測除</em>xxxx yyyy</div>  
  13. </div>  

類名切換(ms-class, ms-hover, ms-active)

avalon提供了多種方式來綁定類名,有ms-class, ms-hover, ms-active, 具體可看這裏

事件綁定(ms-on)

avalon通過ms-on-click或ms-click進行事件綁定,並在IE對事件對象進行修復,並統一了所有瀏覽器對return false的處理。具體可看這裏

avalon並沒有像jQuery設計一個近九百行的事件系統,連事件回調的執行順序都進行修復(IE6-8,attachEvent添加的回調在執行時並沒有按先入先出的順序執行),只是很薄的一層封裝,因此性能很高。

  • ms-click
  • ms-dblclick
  • ms-mouseout
  • ms-mouseover
  • ms-mousemove
  • ms-mouseenter
  • ms-mouseleave
  • ms-mouseup
  • ms-mousedown
  • ms-keypress
  • ms-keyup
  • ms-keydown
  • ms-focus
  • ms-blur
  • ms-change
  • ms-on-*

顯示綁定(ms-visible)

avalon通過ms-visible="bool"實現對某個元素顯示隱藏控制,對於低版本的瀏覽器,它用的是style.display="none"進行隱藏,對於支持HTML5的瀏覽器,它是使用hidden屬性來控制。因此它是優於其他MVVM的實現。

插入綁定(ms-if)

這個功能是抄自knockout的,ms-if="bool",同樣隱藏,但它是將元素移出DOM。這個功能直接影響到CSS :empty僞類的渲染結果,因此比較有用。

雙工綁定(ms-duplex)

這功能抄自angular,原名ms-model起不得太好,姑且認爲利用VM中的某些屬性對錶單元素進行雙向綁定。打算啓用一個新名字叫ms-duplex

這個綁定,它除了負責將VM中對應的值放到表單元素的value中,還對元素偷偷綁定一些事件,用於監聽用戶的輸入從而自動刷新VM。具體如下:

text, password, textarea
默認是通過input事件進行監聽,舊式IE是通過propertychange實現,換言之,每改一個字符串都觸發。如果想在失去焦點時才觸發,可以在元素上使用data-event="change"進行調整。 它要求VM對應的屬性爲一個字符串或數字,不過觸發一次之後,屬性就會變成字符串。
radio
默認是通過change事件進行監聽,舊式IE是通過chick實現, 它要求VM對應的屬性爲一個布爾。
checkbox
默認是通過change事件進行監聽, 它要求VM對應的屬性爲一個字符串數組。
select
默認是通過change事件進行監聽, 它要求VM對應的屬性爲一個字符串或字符串數組(視multiple的值)。

樣式綁定(ms-css)

用法爲ms-css-name="value"

  1.              
  2. <div ms-css-width="aaa"></div>  
  3.         

數據綁定(ms-data)

用法爲ms-data-name="value", 用於爲元素節點綁定HTML5 data-*屬性。

布爾屬性綁定

這主要涉及到表單元素幾個非常重要的布爾屬性,即disabed, readyOnly, selected , checked, 分別使用ms-disabled, ms-enabled, ms-readonly, ms-checked, ms-selected。ms-disabled與ms-enabled是對立的,一個true爲添加屬性,另一個true爲移除屬性。

字符串屬性綁定

這主要涉及到幾個非常常用的字符串屬性,即href, src, alt, title, value, 分別使用ms-href, ms-src, ms-alt, ms-title, ms-value。它們的值的解析情況與其他綁定不一樣,如果值沒有{{}}插值表達式,那麼就當成VM中的一個屬性,並且可以與加號,減號混用, 組成表達式,如果裏面有表達式,整個當成一個字符串。

  1. <a ms-href="aaa + '.html'">xxxx</a>  
  2. <a ms-href="{{aaa}}.html">xxxx</a>  

萬能屬性綁定(ms-attr)

ms-attr-name="value",這個允許我們在元素上綁定更多種類的屬性,如className, tabIndex, name, colSpan什麼的。

萬能綁定(ms-bind)

ms-bind是一種非常強大的同步機制,因爲它允許你持續監聽某一個VM屬性的變化,並且它的參數是一個函數,this又是指向綁定屬性的元素節點,因此比ms-css, ms-attr, ms-data, ms-click等有着因定DOM操作的綁定來得更靈活。

用法: ms-bind-prop="callback", 其中prop, callback都要求來自同一個VM。callback爲一個函數,this指向元素節點。

  1. <div ms-controller="test">  
  2.      <div ms-bind-aaa="callback"></div>  
  3.      <button ms-click="one">點我</button>  
  4. </div>  
  5.   
  6.        
  1. avalon.define("test"function(vm) {  
  2.      vm.aaa = 1111;  
  3.      vm.callback = function() {  
  4.          this.innerHTML = vm.aaa  
  5.      }  
  6.      vm.one = function() {  
  7.          vm.aaa = new Date - 0  
  8.      }  
  9.  });  
  10.    

循環綁定(ms-each)

用法爲ms-each-xxx="array", 其中xxx可以隨意改,如yyy, el, 它是用於在子元素中進行引用。array對應VM中的一個普通數組或一個監控數組。詳見這裏

  1. <script>  
  2.     avalon.define("test", function(vm) {  
  3.         vm.array = [{value: "aaa", text: "111"}, {value: "bbb", text: "222"}, {value: "bbb", text: "333"}]  
  4.     })  
  5. </script>  
  6. <div ms-controller="test">  
  7.     <select ms-each-el="array">  
  8.         <option ms-value="el.value">{{$index}}、{{el}}</option>  
  9.     </select>  
  10. </div>  

UI綁定(ms-ui)

它的格式爲ms-ui-$opts="uiName", 其他$opts可有可無,存在時對應VM中的一個對象,建議將它設置爲不可監控的,因爲它只是作爲一個配置對象。uiName爲控件的名字。

此外,在綁定元素上還應該設置一個data-id屬性,用於指定生成的UI控件對應的VM的名字。你也可以設置更多的data-*屬性,方便用於配置UI。

下面是一個完整的實例用於教導你如何定義使用一個UI。

例子

首先,以AMD規範定義一個模塊,文件名爲avalon.testui.js,把它放到與avalon.js同一目錄下。內容爲:

  1. define(["avalon"],function( av ) {  
  2.    //UI  控件的模板  
  3.   // 必須 在avalon.ui上註冊一個函數,它有四個參數,最後一個是可選的,其他分別爲容器元素,VM的ID名, vmodels  
  4.     av.ui["testui"] = function(element, id, vmodels, opts) {  
  5.         opts = opts || {}  
  6.         var model = av.define(id, function(vm) {  
  7.             vm.name = "這是控件的默認內容"  
  8.         })  
  9.         for (var i in opts) {  
  10.             if (model.hasOwnProperty(i)) {//必須要用hasProperty,因爲model在IE6-8爲一個VBS對象,不允許添加新屬性  
  11.                 model[i] = opts[i]  
  12.             }  
  13.         }  
  14.         //必須在nextTick的回調裏插入新節點 與 進行掃描  
  15.         av.nextTick(function() {  
  16.             element.innerHTML = "<div>{{ name }}</div>"  
  17.             //這裏的格式是固定的  
  18.             av.scan(element, [model].concat(vmodels))  
  19.         })  
  20.         return model //這裏必須返回VM對象,好讓avalon.bindingHandlers.ui方法,將它放到avalon.vmodels中  
  21.     }  
  22.     return av //必須有返回值  
  23. })  
  24.   
  25.   
  26.               

然後頁面這樣使用它

  1. <script>  
  2.   
  3.     require("avalon.testui", function() {  
  4.         avalon.define("test", function(vm) {  
  5.             vm.$opts = {  
  6.                 name: "這是控件的內容"  
  7.             }  
  8.         })  
  9.         avalon.scan()  
  10.         console.log(avalon.vmodels.ddd)  
  11.     })  
  12.   
  13.   
  14. </script>  
  15.   
  16. <div ms-controller="test" ms-ui-$opts="testui" data-id="ddd"></div>  
  17.   
  18.       

$watch

這是一個位於VM的方法,用於監聽VM的某人屬性的變化,回調中有兩個傳參,新屬性值與舊屬性值,裏面的this指向VM,詳見這裏

過濾器

avalon從angular中抄來管道符風格的過濾器,但有點不一樣。 它只能用於{{}}插值表達式。如果不存在參數,要求直接跟|filter,如果存在參傳,則要用小括號括起,參數要有逗號,這與一般的函數調用差不多,如|truncate(20,"……")

avalon自帶以下幾個過濾器

html
沒有傳參,用於將文本綁定轉換爲HTML綁定
uppercase
大寫化
lowercase
小寫化
truncate
對長字符串進行截短,truncate(number, truncation), number默認爲30,truncation爲“...”
camelize
駝峯化處理
escape
對類似於HTML格式的字符串進行轉義,把尖括號轉換爲&gt; &lt;
currency
對數字添加貨幣符號,以及千位符, currency(symbol)
number
對數字進行各種格式化,這與與PHP的number_format完全兼容, number(decimals, dec_point, thousands_sep),
             decimals	可選,規定多少個小數位。
             dec_point	可選,規定用作小數點的字符串(默認爲 . )。
            thousands_sep	可選,規定用作千位分隔符的字符串(默認爲 , ),如果設置了該參數,那麼所有其他參數都是必需的。
                
date
對日期進行格式化,date(formats)
  1. 'yyyy': 4 digit representation of year (e.g. AD 1 => 0001, AD 2010 => 2010)  
  2. 'yy': 2 digit representation of year, padded (00-99). (e.g. AD 2001 => 01, AD 2010 => 10)  
  3. 'y': 1 digit representation of year, e.g. (AD 1 => 1, AD 199 => 199)  
  4. 'MMMM': Month in year (January-December)  
  5. 'MMM': Month in year (Jan-Dec)  
  6. 'MM': Month in year, padded (01-12)  
  7. 'M': Month in year (1-12)  
  8. 'dd': Day in month, padded (01-31)  
  9. 'd': Day in month (1-31)  
  10. 'EEEE': Day in Week,(Sunday-Saturday)  
  11. 'EEE': Day in Week, (Sun-Sat)  
  12. 'HH': Hour in day, padded (00-23)  
  13. 'H': Hour in day (0-23)  
  14. 'hh': Hour in am/pm, padded (01-12)  
  15. 'h': Hour in am/pm, (1-12)  
  16. 'mm': Minute in hour, padded (00-59)  
  17. 'm': Minute in hour (0-59)  
  18. 'ss': Second in minute, padded (00-59)  
  19. 's': Second in minute (0-59)  
  20. 'a': am/pm marker  
  21. 'Z': 4 digit (+sign) representation of the timezone offset (-1200-+1200)  
  22. format string can also be one of the following predefined localizable formats:  
  23.   
  24. 'medium': equivalent to 'MMM d, y h:mm:ss a' for en_US locale (e.g. Sep 3, 2010 12:05:08 pm)  
  25. 'short': equivalent to 'M/d/yy h:mm a' for en_US locale (e.g. 9/3/10 12:05 pm)  
  26. 'fullDate': equivalent to 'EEEE, MMMM d,y' for en_US locale (e.g. Friday, September 3, 2010)  
  27. 'longDate': equivalent to 'MMMM d, y' for en_US locale (e.g. September 3, 2010  
  28. 'mediumDate': equivalent to 'MMM d, y' for en_US locale (e.g. Sep 3, 2010)  
  29. 'shortDate': equivalent to 'M/d/yy' for en_US locale (e.g. 9/3/10)  
  30. 'mediumTime': equivalent to 'h:mm:ss a' for en_US locale (e.g. 12:05:08 pm)  
  31. 'shortTime': equivalent to 'h:mm a' for en_US locale (e.g. 12:05 pm)  
  32.              

例子:

生成於{{ new Date | date("yyyy MM dd:HH:mm:ss")}}

生成於{{ "2011/07/08" | date("yyyy MM dd:HH:mm:ss")}}

生成於{{ "2011-07-08" | date("yyyy MM dd:HH:mm:ss")}}

生成於{{ "01-01-2000" | date("yyyy MM dd:HH:mm:ss")}}

生成於{{ "03 04,2000" | date("yyyy MM dd:HH:mm:ss")}}

生成於{{ "3 4,2000" | date("yyyy MM dd:HH:mm:ss")}}

生成於{{ 1373021259229 | date("yyyy MM dd:HH:mm:ss")}}

生成於{{ "1373021259229" | date("yyyy MM dd:HH:mm:ss")}}

值得注意的是,new Date可傳的格式類型非常多,但不是所有瀏覽器都支持這麼多,詳看這裏

多個過濾器一起工作

  1. <div>{{ prop | filter1 | filter2 | filter3(args, args2) | filter4(args)}}</div>  

如果想自定義過濾器,可以這樣做

  1. avalon.filters.myfilter = function(str, args, args2){//str爲管道符之前計算得到的結果,默認框架會幫你傳入,此方法必須返回一個值  
  2.    /* 具體邏輯 */  
  3.    return ret;  
  4. }  

AMD 加載器

avalon裝備了AMD模範的加載咕咕,這涉及到兩個全局方法 require與define

require(deps, callback)

deps 必需。String|Array。依賴列表,可以是具體路徑或模塊標識,如果想用字符串表示多個模塊,則請用“,”隔開它們。

callback 必需。Function。回調,當用戶指定的依賴以及這些依賴的依賴樹都加載執行完畢後,纔會安全執行它。

模塊標識

一個模塊標識就是一個字符串,通過它們來轉換成到對應JS文件或CSS文件的路徑。

有關模塊標識的CommonJS規範,可以見 這裏

具體約定如下:

  1. 每個模塊標識的字符串組成只能是合法URL路徑,因此只能是英文字母,數字,點號,斜扛,#號。
  2. 如果模塊標識是 以"./"開頭,則表示相對於它的父模塊的目錄中找。
  3. 如果模塊標識是 以"../"開頭,則表示相對於它的父模塊的父目錄中找。
  4. 如果模塊標識不以點號或斜扛開始,則有以下三種情況 
    1. 如果此模塊標識在 $.config.alias存在對應值,換言之某一模塊定義了一個別名,則用此模塊的具體路徑加載文件。
    2. 如果此模塊標識 以http://、https://、file:/// 等協議開頭的絕對路徑,直接用它加載文件。
    3. 否則我們將在引入框架種子模塊(avalon.js)的目錄下尋找是否有同名JS文件,然後指向它。
  5. 對於JS模塊,它可以省略後綴名,即“.js”可有可無;如果想加載CSS文件,請使用css!前綴做標識。
  6. 框架種子模塊的目錄保存於 $.config.base屬性中。
  7. "ready!"是系統佔位符,用於表示DOM樹是否加載完畢,不會進行路徑轉換。

如果想禁止使用avalon自帶的加載器,可以在第一次調用require方法之前,執行如下代碼:

[javascript] view plaincopyprint?
  1. avalon.config({loader: false})  
例子

加載單個模塊。

[javascript] view plaincopyprint?
  1. // 由於lang.js與mass.js是位於同一目錄下,可以省略./  
  2. require("lang"function(lang) {  
  3.     alert(lang.String.toUpperCase("aa"))  
  4. });  
例子

加載多個模塊。需要注意的是,涉及DOM操作時必須要待到DOM樹建完才能進入,因此我們在這裏指定了一個標識,叫"ready!", 它並不一個模塊,用戶自定義模塊,也不要起名叫"ready!"。

[javascript] view plaincopyprint?
  1. require("jquery,node,attr,ready!"function($) {  
  2.     alert($.fn.attr + "");   
  3.     alert($.fn.prop + "");  
  4. });  
例子

加載多個模塊,使用字符串數組形式的依賴列表。

[javascript] view plaincopyprint?
  1. require(["jquery""css""ready!"], function($, css) {  
  2.     $("#js_require_ex3").toggle();  
  3. });  
例子

加載CSS文件。

[javascript] view plaincopyprint?
  1. require(["jquery""ready!""css!http//sdfds.xdfs.css"], function($) {  
  2.     $("#js_require_ex3").toggle();  
  3. });  
例子

使用別名機制管理模塊的鏈接。

[javascript] view plaincopyprint?
  1. var path = location.protocol + "//" + location.host + "/doc/scripts/loadtest/"  
  2. require.config({  
  3.     alias: {  
  4.         "aaa": path + "aaa.js",  
  5.         "bbb": path + "bbb.js",  
  6.         "ccc": path + "ccc.js",  
  7.         "ddd": path + "ddd.js"  
  8.     }  
  9. })  
  10. require("aaa,bbb,ready"function(a, b, $) {  
  11.     var parent = $("#loadasync2")  
  12.     parent.append(a);  
  13.     parent.append(b);  
  14.     $("#asynctest2").click(function() {  
  15.         require("ccc,ddd"function(c, d) {  
  16.             parent.append(c);  
  17.             parent.append(d);  
  18.         })  
  19.     })  
  20. });  
例子

加載不按規範編寫的JS文件,可以讓你不用改jQuery的源碼就加載它。相當於其他加載器的shim插件。 與別名機制不同的是,現在它對應一個對象,src爲完整路徑,deps爲依賴列表,exports爲其他模塊引用它時,傳送給它們的參數

[javascript] view plaincopyprint?
  1. !function() {  
  2.                var path = "http://files.cnblogs.com/shuicaituya/"  
  3.                require.config({  
  4.                    alias: {  
  5.                        "jquery": {  
  6.                            "src": path + "jquery.js",  
  7.                            deps: [], //沒有依賴可以不寫  
  8.                            exports: "jQuery"  
  9.                        }  
  10.                    }  
  11.                });  
  12.                require("jquery"function($) {  
  13.                    alert($)  
  14.                    alert("回調調起成功");  
  15.                })  
  16.            }()  
  17.              

define方法用於定義一個模塊,格式爲:

define( id?, deps?, factory )

id
可選。String。模塊ID。它最終會轉換一個URL,放於 $.modules中。
deps
可選。String|Array。依賴列表。
factory
必需。Function|Object。模塊工廠。它的參數列參爲其依賴模塊所有返回的值,如果某個模塊沒有返回值,則對應位置爲undefine
        
例子

加載不按規範編寫的JS文件,可以讓你不用改jQuery的源碼就加載它。相當於其他加載器的shim插件。 與別名機制不同的是,現在它對應一個對象,src爲完整路徑,deps爲依賴列表,exports爲其他模塊引用它時,傳送給它們的參數

[javascript] view plaincopyprint?
  1. //aaa.js 沒有依賴不用改  
  2. define("aaa"function() {  
  3.     return 1  
  4. })  
  5.   
  6. //bbb.js  沒有依賴不用改  
  7. define("bbb"function() {  
  8.     return 2  
  9. });  
  10. //ccc.js  
  11. define("ccc", ["$aaa"], function(a) {  
  12.     return 10 + a  
  13. })  
  14.   
  15. //ddd/ddd.js  
  16. define("ddd", ["$ddd"], function(c) {  
  17.     return c + 100  
  18. });  
發佈了92 篇原創文章 · 獲贊 36 · 訪問量 67萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章