錨點路由 8.1. 路由定義 8.2. 參數定義 8.3. 業務處理 定義模板變量標識標籤 AJAX 10.1. HTTP請求 10.2. 廣義回調管理 工具函數 11.1. 上下文綁定 11.2. 對象處理 11.3. 類型斷定 其它服務 12.1. 日誌 12.2. 緩存 12.3. 計時器 12.4. 表達式函數化 12.5. 模板單獨使用 自定義模塊和服務 13.1. 模塊和服務的概念與關係 13.2. 定義模塊 13.3. 定義服務 13.4. 引入模塊並使用服務 附加模塊 ngResource 14.1. 使用引入與總體概念 14.2. 基本定義 14.3. 基本使用 14.4. 定義和使用時的佔位量 http://zouyesheng.com/angular.html
關於AvalonJS
avalon是一個迷你的MVVM框架,雖然從發佈到如今,它臌脹了很多,但它如今仍是比knockout小許多。avalon開發過程一直遵循三個原則:1,複雜即錯誤,2,數據結構優於算法,3,出奇制勝。這三大原則保證avalon具備良好的維護性,擴展性,不同凡響。javascript
簡單說一下其餘三大MVVM的實現思路:css
- knockout:最先冒出來的JS MVVM庫,經過轉換VM中全部要監聽的東西爲函數,而後執行它們,獲得某一時刻中,一共有多少函數被執行,將它們放到棧中,最底的就是最早被執行的,它上面的就是此函數所依賴的函數,從而獲得依賴關係。 而後設計一個觀察者模式,從上面的依賴檢測中,將依賴函數做爲被依賴者(最早執行的那個的)的訂閱者,之後咱們對被依賴者進行賦值時,就會通先訂閱者更新自身,從而造成一個雙向綁定鏈。 而且,knockout會將視圖中的綁定屬性進行轉換,分解出求值函數與視圖刷新函數,視圖刷新函數依賴於求值函數,而求值函數亦依賴於咱們VM中的某些屬性(這時,它們都轉換爲函數),在第一次掃描時,它們會加入對應屬性的訂閱者列隊中, 從而VM中的某個屬性改變,就會自動刷新視圖。
評價:實現很是巧妙,是avalon0.1-0.3的重要學習對象,但將屬性變成一個函數,讓人用點不習慣,許多用法都有點笨笨的。 雖然是一個輕盈的庫,但擴展性不強,裏面的實現異常複雜,致使能參與源碼的人太少。
- emberjs: 一個大而全的框架,一應俱全。一開始是使用Object.defineProperty+觀察者實現,但IE8的問題,讓它不得不啓用上帝setter, 上帝getter。沒有自動收集依賴的機制,沒有監控數組,計算屬性須要本身指定依賴。VM可繼承。 VM與視圖的雙向綁定依賴於其強大無比上萬行的Handlebars 模板。據說是外國目前最好用的MV*框架。由於做者既是jQuery的核心成員,也是Rails的核心成員,雖然因爲兼容問題沒實現自動收集依賴,但框架的其餘方面作得很是易上手,人性化。
評價:太大了,優缺點同python的Django框架。
- 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手動觸發。
評價:乍一看入門很是容易,其實相反。條條框框太多,適合那些一直與JAVA搏鬥的人士。對於那些接受過jQuery洗禮的人來講,用着用着會造反的……
如今的avalon是我在徹底消化了knockout發展起來的,準確來講,是0.4版,經過Object.defineProperties與VBScript實現了與普通對象看起來沒什麼兩樣的VM,VM裏面充滿了訪問器屬性,而訪問器屬性確定對應一個setter,一個getter, 咱們就在setter, getter中走knockout的老路,實現自動收集依賴,而後放進一個簡單的觀察者模式中,從而實現雙向綁定。將綁定屬性分解爲求值函數與視圖刷新函數,早前,avalon也與knockout同樣使用一個簡單的parser,而後經過with實現, 0.82一個新的parser 上馬,一樣的迷你,但生成的求值函數,更方便依賴收集,而且沒有with語句,性能更佳。angular也不是一無可取,我也從它那裏抄來了{{}}插值表達式,過濾器機制,控制器綁定什麼的。 html
avalon在內部使用了許多巧妙的設計,所以能涵蓋angular絕對大多數功能,但體積卻很是少。此外,在性能上,如今除了chrome外,它都比knockout快,angular則是最慢的。 在移動端上,avalon這個優點會被大大放大化的。java
關於avalon的幾點:node
- 兼容IE6
- 配套着AJAX,動畫,路由器,加載器,拖放等模塊與功能
- avalon會自動同步視圖,不用操做DOM,所以寫出的業務代碼比jQuery更少,從而維護性更好
迷你MVVM框架在github的倉庫https://github.com/RubyLouvre/avalon, 若是你要兼容IE6,那麼下其中的avalon.js, 若是你只打算兼容IE10與標準瀏覽器,那麼下avalon.mobile.js。python
官網地址http://rubylouvre.github.io/mvvm/jquery
開始的例子
咱們從一個完整的例子開始認識 avalon :git
< meta http-equiv = "Content-Type" content = "text/html; charset=UTF-8" > |
< script src = "avalon.js" ></ script > |
< div ms-controller = "box" > |
< div style = " background: #a9ea00;" ms-css-width = "w" ms-css-height = "h" ms-click = "click" ></ div > |
< p >W: < input type = "text" ms-duplex = "w" data-duplex-event = "change" /></ p > |
< p >H: < input type = "text" ms-duplex = "h" /></ p > |
avalon.define("box", function(vm) { |
vm.w = parseFloat(vm.w) + 10; |
vm.h = parseFloat(vm.h) + 10; |
上面的代碼中,咱們能夠看到在JS中,沒有任何一行操做DOM的代碼,也沒有選擇器,很是乾淨。在HTML中, 咱們發現就是多了一些以ms-開始的綁定屬性與{{}}插值表達式,有的是用於渲染樣式, 有的是用於綁定事件。在ms-duplexl中,咱們會發現它會反過來操做VM,VM的改變也會影響視圖的其餘部分。 github
掃描
不過上面的代碼並不完整,它能工做,是由於框架默認會在DOMReady時掃描DOM樹,將視圖中的綁定屬性與{{}}插值表達式抽取出來,轉換爲求值函數與視圖刷新函數。ajax
上面的JS代碼至關於:
avalon.ready( function () { |
avalon.define( "box" , function (vm) { |
vm.w = parseInt(vm.w) + 10; |
vm.h = parseInt(vm.h) + 10; |
avalon.scan是一個很是重要的方法,它有兩個可選參數,第一個是掃描的起點元素,默認是HTML標籤,第2個是VM對象。
avalon.scan = function (elem, vmodel) { |
var vmodels = vmodel ? [].concat(vmodel) : [] |
視圖模型
咱們是經過avalon.define函數返回一個視圖對象VM,而且avalon.define(vmName, function(vm){})中的vm並不等於VM,工廠函數中的vm是用於轉換爲VM的。生成的VM比用戶指定的屬性還多了許多屬性。
默認的,除了函數外,其餘東西都轉換爲監控屬性,計算屬性與監控數組。若是不想讓它轉換,可讓此屬性以 $開頭,框架就不會轉換它們。
若是實在不方便更名,又不想被轉換,好比是一個jQuery對象或一個DOM節點,若是轉換,確定拖死框架,咱們能夠放到vm.$skipArray = [propName1, propName2] ($skipArray是一個字符串數組,不能放其餘類型的東西進去)中去,這樣也忽略轉換。視圖裏面,咱們可使用ms-controller, ms-important指定一個VM的做用域。此外,在ms-each, ms-with中,它們會建立一個臨時的VM,用於放置$key, $val, $index, $last, $first, $remove等變量或方法。
另外,avalon不容許在VM定義以後,再追加新屬性與方法,好比下面的方式是錯誤的:
var vm = avalon.define( "test" , function (vm) { |
vm.test1 = '點擊測試按鈕沒反應 綁定失敗' ; |
此外,不要在avalon.define方法裏面執行函數或方法,所以框架會對define執行了兩次,第1次用於取得用戶對vm對象(factory的傳參)的設置,第2次用於重置裏面的vm對象爲真正的VM。看源碼:
avalon.define = function (name, factory) { |
if ( typeof name !== "string" ) { |
if ( typeof factory !== "function" ) { |
avalon.error( "factory必須是函數" ) |
var model = modelFactory(scope) |
return VMODELS[name] = model |
所以下面的寫法會執行兩次alert
function check(){ alert( "!!!!!!!!!!!" )} |
var model = avalon.define( "xxx" , function (vm){ |
若是VM中的某函數是做爲事件回調而存在,如ms-click=aaa, 若是想兼容IE6,aaa的vm最好替換爲avalon.define返回的變量,確保能正確運行。
var model = avalon.define( "xxx" , function (vm){ |
全部定義好的VM都會儲放在avalon.vmodels中!
咱們再看看如何更新VM中的屬性(重點):
var model = avalon.define("update", function(vm) { |
vm.simpleArray = [1, 2, 3, 4] |
vm.objectArray = [{name: "a"}, {name: "b"}, {name: "c"}, {name: "d"}] |
vm.simpleArray = [1, 2, 3, 4] |
vm.objectArray = [{name: "a", value: "aa"}, {name: "b", value: "bb"}, {name: "c", value: "cc"}, {name: "d", value: "dd"}] |
//若是是更新簡單數據類型(string, boolean, number)或Date類型 |
model.time = new Date(date.setFullYear(2005)) |
//只能全是字符串,或是全是布爾,不能有一些是這種類型,另外一些是其餘類型 |
//這時咱們可使用set方法來更新(它有兩個參數,第一個是index,第2個是新值) |
model.simpleArray.set(0, 1000) |
model.simpleArray.set(2, 3000) |
model.objectArray.set(0, {name: "xxxxxxxxxxxxxxxx", value: "xxx"}) |
model.objectArray[1].name = "5555" |
//若是要更新對象,直接賦給它一個對象,注意不能將一個VM賦給它,能夠到VM的$model賦給它(要不會在IE6-8中報錯) |
< div ms-controller = "update" > |
< div >{{time | date("yyyy - MM - dd mm:ss")}}</ div > |
< ul ms-each = "simpleArray" > |
< div > < select ms-each = "objectArray" > |
< option ms-value = "el.value" >{{el.name}}</ option > |
< li >{{$key}} {{$val}}</ li > |
這裏還有個例子,你們認真看看。
數據模型
當咱們要用AJAX與後端交互時,若是直接把VM傳上去太大了,這時咱們須要把它對應的純數組的JS對象。在VM中有個叫$model的屬性,這是一個對象,就是數據模型M了。當咱們更改VM時,框架就會自動同步M
綁定屬性與動態模板
在開始以前,咱們看一下靜態模板是怎麼工做的:
我以前寫了一個叫ejs的靜態模板引擎:
< script type = "tmpl" id = "table_tmpl" > |
<&- for(var i=0,tl = @trs.length,tr;i< tl ;i++){ -&> |
< td ><&= tr.name;; &></ td > < td ><&= tr.age; &></ td > < td ><&= tr.sex || "男" &></ td > |
它是以一個script標籤作容器,裏面的整個叫模板。模板裏面有許多以 <& 與 &>劃分出來的區塊,用於插入JS代碼,以@開頭的變量是對應於數據包中的某個屬性。
幾乎全部靜態模板的實現原理都是同樣的,將這個模板變成一個函數,而後裏面分紅靜態部分與動態部分,靜態部分就是上面的HTNMl部分,轉換爲一個個字符串,動態部分就是插入的JS代碼, 它們基本上原封不動地成爲函數體的邏輯。而後咱們傳入一個對象給這個函數,最後獲得一個符合HTML格式的字符串,最後用它貼到頁面上某個位置就好了。
靜態模板有幾個缺點,首先它容易混入大量的JS邏輯,對於菜鳥來講,他們特別喜歡在裏面放入愈來愈多JS代碼。這個在JSP年代,已經證實是bad practice。爲此出現了logic-less的 mustache。 其次,它更新視圖老是一大片一大片地處理,改動太大。最後,是因爲第2點引起的問題,它對事件綁定等不友好,由於一更新,原來的節點都被消滅了,須要從新綁定。幸虧,jQuery普及了事件代理,這問題纔沒有 暴露出來。
再次,字符串模塊沒有對樣式的操做,流程的操做進行封裝,沒有計算屬性,監控數組的東西,很容易誘導用戶在頁面上寫大量業務邏輯,致使代碼沒法維護。
下面就是一個PHP+原生JS+JQ的例子:
再看動態模板,幾乎全部MVVM框架都用動態模板(固然也有例外,如emberjs)。動態模板以整個DOM樹爲容器,它經過掃描方式進行第一次更新視圖。 在靜態模板,經過<& 與 &>劃分的部分,轉換爲綁定屬性與{{}}插值表達式(這是一種文本綁定,在avalon中,咱們能夠經過|html過濾器,轉換html綁定) 這樣就有效阻止用戶在頁面上寫邏輯。雖然動態模板也支持ms-if, ms-each等表示邏輯關係的綁定,但它的值最複雜也只能是一個表達式。 在綁定屬性中,屬性名用於指定操做行爲,如切換類名,控制顯示,循環渲染,綁定事件,數據填充什麼的,而屬性值是決定這些操做是否執行,與渲染結果。 因爲雙向綁定的關係,它不像靜態模板那樣,每次都要本身將數據包放進函數,獲得結果,而後innerHTML刷新某個區域。它是在用戶爲VM的某個屬性進行從新賦值,將視圖中對應的某個文本節點, 特性節點或元素節點的值進行重刷。所以不會影響事件綁定。
在avalon中,這些視圖刷新函數都有個element屬性,保持對應的元素節點,每次同步時,都會檢測此元素節點是否在DOM樹,不在DOM樹就取消訂閱此刷新函數,節約內存,防止無效操做。
所以,大家能夠看區別了吧。綁定屬性與插值表達式就是對應靜態模板中的JS邏輯部分,因爲只容許爲表達式或單個屬性值,複雜度被控制了,強制用戶將它們轉移到VM中。 VM做爲一個數據源,對應靜態模板的數據包,而且多了一個自動觸發功能,進化成一個消息中心。
< p ms-controller = "test" ms-click = "click" >{{ a }}</ p > |
avalon.define("test", function(vm) { |
做用域綁定(ms-controller, ms-important)
avalon提供ms-controller, ms-important來指定VM在視圖的做用範圍。其餘ms-*綁定屬性或{{}}插值表達式,必須置於它們的做用域範圍才能生效。好比有兩個VM,它們都有一個firstName屬性,在DIV中,若是咱們用 ms-controller="VM1", 那麼對於DIV裏面的{{firstName}}就會解析成VM1的firstName中的值。
注意,avalon每掃描一個元素時,會移除對應的綁定屬性或同名類名。這個特性很是有用,假如咱們在頁面使用了大量{{}}插值表達式,網速慢時,就會被用戶看到,認爲是亂碼了。爲了預防這種狀況, 咱們能夠在樣式中定義.ms-controller, [ms-controller]{ display:none},那麼當元素被掃描後,它們纔會安全顯示出來。
有關它們的詳細用法,可見這裏。
模板綁定(ms-include)
若是單是把DOM樹做爲一個模板遠遠不夠的,好比有幾個地方,須要重複利用一套HTML結構,這就要用到內部模板或外部模板了。
內部模板是,這個模板與目標節點是位於同一個DOM樹中。咱們用一個MIME不明的script標籤或者noscript標籤(0.94後支持,建議使用它)保存它,而後經過ms-include="id"引用它。
< meta http-equiv = "content-type" content = "text/html; charset=UTF-8" /> |
< script src = "avalon.js" ></ script > |
avalon.define("test", function(vm) { |
< script type = "avalon" id = "tpl" > |
< div ms-controller = "test" > |
< div ms-include = "'tpl'" ></ div > |
注意,ms-include的值要用引號括起,表示這只是一個字符串,這時它就會搜索頁面的具備此ID的節點,取其innerHTML,放進ms-include所在的元素內部。不然這個tpl會被當成一個變量, 框架就會在VM中檢測有沒有此屬性,有就取其值,重複上面的步驟。若是成功,頁面會出現here, 2的字樣。
若是你們想在模板加載後,加工一下模板,可使用data-include-loaded來指定回調的名字。
若是你們想在模板掃描後,隱藏loading什麼的,可使用data-include-rendered來指定回調的名字。
下面是它們的實現
var vmodels = data.vmodels |
var rendered = getBindingCallback(elem.getAttribute("data-include-rendered"), vmodels) |
var loaded = getBindingCallback(elem.getAttribute("data-include-loaded"), vmodels) |
function scanTemplate(text) { |
text = loaded.apply(elem, [text].concat(vmodels)) |
avalon.innerHTML(elem, text) |
rendered && checkScan(elem, function() { |
外部模板,一般用於多個頁面的複用,所以須要整成一個獨立的文件。這時咱們就須要經過ms-include-src="src"進行加載。
好比有一個HTML文件tmpl.html,它的內容爲:
< div >它是經過AJAX的GET請求加載下來的</ div > |
而後咱們這樣引入它
< div ms-include-src = "'tmpl.html'" ></ div > |
有關它的高級應用的例子可見這裏利用ms-include與監控數組實現一個樹
注意,ms-include-src須要後端服務器支持,由於用到同域的AJAX請求。
數據填充(ms-text, ms-html)
這分兩種:文本綁定與HTML綁定,每種都有兩個實現方式
avalon.define("test", function(vm) { |
vm.text = "< b > 1111 </ b >" |
< 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 > |
默認狀況下,咱們是使用{{ }} 進行插值,若是有特殊需求,咱們還能夠配置它們:
注意,你們不要用<, > 做爲插值表達式的界定符,由於在IE6-9裏可能轉換爲註釋節點,詳見這裏
插值表達式{{}}在綁定屬性的使用,只限那些能返回字符串的綁定屬性,如ms-attr、ms-css、ms-include、ms-class、 ms-href、 ms-title、ms-src等。一旦出現插值表達式,說明這個整個東西分紅可變的部分與不可變的部分,{{}}內爲可變的,反之亦然。 若是沒有{{}}說明整個東西都要求值,又如ms-include="'id'",要用兩種引號強制讓它的內部不是一個變量。
類名切換(ms-class, ms-hover, ms-active)
avalon提供了多種方式來綁定類名,有ms-class, ms-hover, ms-active, 具體可看這裏
事件綁定(ms-on)
avalon經過ms-on-click或ms-click進行事件綁定,並在IE對事件對象進行修復,具體可看這裏
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-scroll
- ms-animation
- ms-on-*
< script src = "avalon.js" type = "text/javascript" ></ script > |
avalon.ready(function() { |
var a = avalon.define("simple", function(vm) { |
vm.array = ["aaa", "bbb", "ccc"] |
vm.argsClick = function(e, a, b) { |
vm.loopClick = function(a) { |
< fieldset ms-controller = "simple" > |
< div ms-click = "argsClick($event, 100, firstName)" >點我</ div > |
< div ms-each-el = "array" > |
< p ms-click = "loopClick(el)" >{{el}}</ p > |
另外,這裏有一些結合ms-data實現事件代理的技巧,建議事件綁定接口支持事件代理,最簡單就是table上能夠綁定td的點擊事件
顯示綁定(ms-visible)
avalon經過ms-visible="bool"實現對某個元素顯示隱藏控制,它用是style.display="none"進行隱藏。
插入綁定(ms-if)
這個功能是抄自knockout的,ms-if="bool",一樣隱藏,但它是將元素移出DOM。這個功能直接影響到CSS :empty僞類的渲染結果,所以比較有用。
< meta http-equiv = "Content-Type" content = "text/html; charset=utf-8" /> |
< script t src = "avalon.js" ></ script > |
< body ms-controller = "Test" > |
< ul ms-each-item = "array" > |
< li ms-click = "$remove" ms-if = "$index % 2 == 0" >{{ item }} --- {{$index}}</ li > |
< script type = "text/javascript" > |
avalon.define('Test', function(vm) { |
vm.array = "a,b,c,d,e,f,g".split(",") |
這裏得介紹一下avalon的掃描順序,由於一個元素可能會存在多個屬性。總的流程是這樣的:
ms-skip --> ms-important --> ms-controller --> ms-if --> ms-repeat --> ms-if-loop --> ...-->ms-each --> ms-with --> ms-duplex
首先跑在最前面的是 ms-skip,只要元素定義了這個屬性,不管它的值是什麼,它都不會掃描其餘屬性及它的子孫節點了。而後是 ms-important, ms-controller這兩個用於圈定VM的做用域的綁定屬性,它們的值爲VM的$id,它們不會影響avalon繼續掃描。接着是ms-if,因爲一個頁面可能被當成子模塊,被不一樣的VM所做用,那麼就會出現有的VM沒有某個屬性的狀況。好比下面的狀況:
< meta http-equiv = "Content-Type" content = "text/html; charset=utf-8" /> |
< script src = "avalon.js" ></ script > |
< body ms-controller = "Test" > |
< ul ms-if = "array" ms-each-item = "array" > |
< li ms-click = "$remove" ms-if = "$index % 2 == 0" >{{ item }} --- {{$index}}</ li > |
< script type = "text/javascript" > |
avalon.define('Test', function(vm) { |
若是沒有ms-if作代碼防護,確定報一大堆錯。
接着是 ms-repeat綁定。出於某些緣由,咱們不想顯示數組中的某些元素,就須要讓ms-if拖延到它們以後才起做用,這時就要用到ms-if-loop。
< meta http-equiv = "Content-Type" content = "text/html; charset=utf-8" /> |
< script src = "avalon.js" ></ script > |
< body ms-controller = "Test" > |
< li ms-repeat = "array" ms-if-loop = "el" >{{ el }}</ li > |
< script type = "text/javascript" > |
avalon.define('Test', function(vm) { |
vm.array = ["aaa", "bbb", null, "ccc"] |
以後就是其餘綁定,但殿後的老是ms-duplex。從ms-if-loop到ms-duplex之間的執行順序是按這些綁定屬性的首字母的小寫的ASCII碼進行排序,好比同時存在ms-attr與ms-visible綁定,那麼先執行ms-attr綁定。若是咱們想綁定多個類名,用到ms-class, ms-class-2, ms-class-3, ms-class-1,那麼執行順序爲ms-class, ms-class-1, ms-class-2, ms-class-3。若是咱們要用到綁定多個點擊事件,須要這樣綁定:ms-click, ms-click-1, ms-click-2……更具體能夠查看源碼中的scanTag, scanAttr方法。
雙工綁定(ms-duplex)
這功能抄自angular,原名ms-model起不得太好,姑且認爲利用VM中的某些屬性對錶單元素進行雙向綁定。
這個綁定,它除了負責將VM中對應的值放到表單元素的value中,還對元素偷偷綁定一些事件,用於監聽用戶的輸入從而自動刷新VM。具體以下:
-
ms-duplex="prop"
-
當元素爲text, password, textarea時,要求prop爲一個字符串,當咱們改動它的內容時,avalon就會將此元素的value值賦給prop(在默認狀況下,是使用input事件進行綁定,即每改動一個字符,都會進行同步,你們也能夠指定
data-duplex-event="change",改用change事件進行綁定)
當元素爲radio時,要求prop爲一個布爾, 當咱們改動它的內容時,avalon就會將此元素的checked值(布爾)賦給prop
當元素爲checkbox時,要求prop爲一個數組, 當咱們改動它的內容時,avalon就會將此元素的value值push進prop
當元素爲select時,要求prop爲一個字符串或數組(視multiple的值), 當咱們選中它的某一個項時,avalon就會將此option元素的value值或text值(沒有value時)push進prop。
-
ms-duplex-text="prop"
-
只能用於radio,
用於模擬text控件的行爲, 要求prop爲一個字符串,當咱們選中某一個radio時,avalon就會將此元素的value值賦給prop
用於實現多選一
-
ms-duplex-radio="prop"
-
只能用於checkbox,
用於模擬radio控件的行爲, 要求prop爲一個布爾,當咱們選中某一個checkbox時,avalon就會將此元素的checked值(布爾)賦給prop
多用於實現GRID中的全選/全不選功能
-
ms-duplex-bool="prop"
-
只能用於radio, 要求prop爲一個布爾,而且元素的value爲「true」或「false」,當咱們選中某一個radio時,avalon就會將此元素的value轉換爲布爾,賦給對應的prop。
注意:ms-duplex與ms-checked不能在同時使用於一個元素節點上。
注意:若是表單元素同時綁定了ms-duplex=xxx與ms-click或ms-change,而事件回調要當即獲得這個vm.xxx的值,input[type=radio]是存在問題,它不能當即獲得當前值,而是以前的值,須要在回調裏面加個setTimeout。
ms-duplex-text, ms-duplex-radio與ms-duplex的用法,你們能夠經過這個頁面進行學習。
<meta http-equiv= "Content-Type" content= "text/html; charset=UTF-8" > |
<div ms-controller= "box" > |
<li><input type= "checkbox" ms-click= "checkAll" ms-checked= "checkAllbool" />全選</li> |
<li ms-repeat= "arr" ><input type= "checkbox" ms-value= "el" ms-duplex= "selected" />{{el}}</li> |
<script src= "avalon.js" ></script> |
var model = avalon.define( "box" , function (vm) { |
vm.arr = [ "1" , '2' , "3" , "4" ] |
vm.checkAllbool = vm.arr.length === vm.selected.length |
vm.checkAll = function () { |
model.selected.$watch( "length" , function (n) { |
model.checkAllbool = n === model.arr.size() |
對於非radio, checkbox, select的控件,咱們能夠經過data-duplex-changed來指定一個回調,傳參爲元素的value值,this指向元素自己,要求必須有返回值。
< meta http-equiv = "Content-Type" content = "text/html; charset=utf-8" /> |
< title >data-duplex-changed</ title > |
< script src = "avalon.js" ></ script > |
< body ms-controller = "duplex" > |
< input ms-duplex = "username" data-duplex-changed = "callback" > |
< script type = "text/javascript" > |
avalon.define('duplex', function(vm) { |
vm.callback = function(val){ |
return this.value = val.slice(0, 10)//不能超過10個字符串 |
樣式綁定(ms-css)
用法爲ms-css-name="value"
注意:屬性值不能加入CSS hack與important!
< meta http-equiv = "Content-Type" content = "text/html; charset=UTF-8" > |
< script src = "../avalon.js" ></ script > |
avalon.define("test", function(vm) { |
< body ms-controller = "test" > |
< h3 >在舊式IE下,若是父元素是定位元素,但沒有設置它的top, left, z-index,那麼爲它設置透明時, |
< div class = "outer" ms-css-opacity = "o" ms-css-background-color = "bg" > |
< div class = "inner" ></ div > |
數據綁定(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中的一個屬性,而且能夠與加號,減號混用, 組成表達式,若是裏面有表達式,整個當成一個字符串。
< a ms-href = "aaa + '.html'" >xxxx</ a > |
< a ms-href = "{{aaa}}.html" >xxxx</ a > |
屬性綁定(ms-attr)
ms-attr-name="value",這個容許咱們在元素上綁定更多種類的屬性,如className, tabIndex, name, colSpan什麼的。
循環綁定(ms-repeat)
用法爲ms-repeat-xxx="array", 其中xxx能夠隨意命名(注意,不能出現大寫,由於屬性名在HTML規範中,會所有轉換爲小寫,詳見這裏),如item, el。 array對應VM中的一個普通數組或一個監控數組。監控數組擁有原生數組的全部方法,而且比它還多了set, remove, removeAt, removeAll, ensure, pushArray與 clear方法 。詳見這裏。
在早期,avalon提供了一個功能類似的ms-each綁定。ms-each與ms-repeat的不一樣之處在於,前者循環它的孩子(如下圖爲例,可能包含LI元素兩邊的空白),後者循環它自身。
注意,ms-each, ms-repeat會生成一個新的代理VM對象放進當前的vmodels的前面,這個代理對象擁有el, $index, $first, $last, $remove, $outer, $itemName等屬性。另外一個會產生VM對象的綁定是ms-widget。
- el: 不必定叫這個名字,好比說ms-each-item,它就變成item了。默認爲el。指向當前元素。
- $first: 斷定是否爲監控數組的第一個元素
- $last: 斷定是否爲監控數組的最後一個元素
- $index: 獲得當前元素的索引值
- $outer: 獲得外圍循環的那個元素。
- $itemName: 保存el(或item, elem)這個名字。
- $remove:這是一個方法,用於移除此元素
咱們還能夠經過data-repeat-rendered, data-each-rendered來指定這些元素都插入DOM被渲染了後執行的回調,this指向元素節點, 有一個參數表示爲當前的操做,是add, del, move, index仍是clear
vm.rendered = function(action){ |
avalon.log("渲染完畢")//注意,咱們經過vm.array.push(4,5)添加元素,會連續兩次觸發rendered,第一次add,第二次爲index |
< li data-repeat-rendered = "rendered" ms-repeat = "array" >{{el}}</ li > |
< meta http-equiv = "Content-Type" content = "text/html; charset=UTF-8" > |
< script src = "avalon.js" ></ script > |
var a = avalon.define("array", function(vm) { |
vm.array = ["1", "2", "3", "4"] |
var b = avalon.define("complex", function(vm) { |
vm.array = [{name: "xxx", sex: "aaa", c: {number: 2}}, {name: "yyy", sex: "bbb", c: {number: 4}}]// |
a.array.push(5, 6, 7, 8, 9) |
a.array.unshift("a", "b", "c", "d") |
a.array.splice(1, 3, "x", "y", "z") |
< fieldset class = "id2013716" ms-controller = "array" > |
< li >數組的第{{$index+1}}個元素爲{{el}}</ li > |
< p >size: < b style = "color:red" >{{array.size()}}</ b ></ p > |
< fieldset class = "id2013716" ms-controller = "complex" > |
< li ms-repeat-el = "array" >{{el.name+" "+el.sex}}它的內容爲 number:{{el.c.number}}</ li > |
< body ms-controller = "page" > |
{{$index}} < button ms-click = "$remove" >{{el}} 點我刪除</ button > |
< table border = "1" width = "800px" style = "background:blueviolet" > |
{{el}} {{$first}} {{$last}} |
< li ms-repeat = "arr" >< button ms-click = "$remove" >測試{{$index}}</ button >{{el}}</ li > |
< li ms-repeat = "object" >{{$key}}:{{$val}}</ li > |
< li >{{$key}}:{{$val}}</ li > |
< h3 >經過指定data-with-sorted規定只輸出某一部分建值及它們的順序,只能循環對象時有效</ h3 > |
< ol ms-with = "bigobject" data-with-sorted = "order" title = 'with' > |
< li >{{$key}}:{{$val}}</ li > |
< li ms-repeat = "bigobject" data-with-sorted = "order" >{{$key}}:{{$val}}</ li > |
< h3 >ms-repeat實現數組雙重循環</ h3 > |
< table border = "1" style = "background:yellow" width = "400px" > |
< tr ms-repeat = "dbarray" >< td ms-repeat-elem = "el.array" >{{elem}}</ td ></ tr > |
< table border = "1" style = "background:green" width = "400px" > |
< tbody ms-each = "dbarray" > |
< tr ms-each-elem = "el.array" >< td >{{elem}}</ td ></ tr > |
< h3 >ms-with實現對象雙重循環,並經過$outer訪問外面的鍵名</ h3 > |
< div ms-repeat = "dbobjec" >{{$key}}:< strong ms-repeat = "$val" >{{$key}} {{$val}} < span style = "font-weight: normal" >{{$outer.$key}}</ span >| </ strong ></ div > |
< script src = "avalon.js" ></ script > |
var model = avalon.define('page', function(vm) { |
vm.arr = ["a", "b", "c", "d", "e", "f", "g", "h"] |
"kkk": "vvv", "kkk2": "vvv2", "kkk3": "vvv3" |
url: 'data/stockQuote.json', |
return ["name", "sortStatus", "sortName", "method", "align"] |
數組循環綁定(ms-each)
語法與ms-repeat幾乎一致,建議用ms-repeat代替。
對象循環綁定(ms-with)
語法爲 ms-with="obj" 子元素裏面用$key, $val分別引用鍵名,鍵值。另咱們能夠經過指定data-with-sorted回調,規定只輸出某一部分建值及它們的順序。 注意,此綁定已經不建議使用,它將被ms-repeat代替,ms-repeat裏面也可使用data-with-sorted回調。
< meta http-equiv = "Content-Type" content = "text/html; charset=UTF-8" > |
< script type = 'text/javascript' src = "avalon.js" ></ script > |
var a = avalon.define("xxx", function(vm) { |
< body ms-controller = "xxx" > |
< div >{{$key}} {{$val}}</ div > |
< div >{{$key}} {{$val}}</ div > |
< div >{{$key}} {{$val}}</ div > |
有關ms-each, ms-repeat, ms-with更高的用法,如雙重循環什麼的,能夠看這裏
UI綁定(ms-widget)
它的格式爲ms-widget="uiName, id?, optsName?"
- uiName,必選,必定要所有字母小寫,表示組件的類型
- id 可選 這表示新生成的VM的$id,方便咱們從avalon.vmodels[id]中獲取它操做它,若是它等於$,那麼表示它是隨機生成,與不寫這個效果同樣,框架會在uiName加上時間截,生成隨機ID
- optName 可選, 配置對象的名字。指在已有的VM中定義一個對象(最好指定它爲不可監控的外),做爲配置的一部分(由於每一個UI都有它的默認配置對象,而且咱們也能夠用data- uiName? -xxx來作更個性化的處理 )。若是不指optName默認與uiName同名。框架老是找離它(定義ms-widget的那個元素節點)最近的那個VM來取這個配置項。若是這個配置項裏面有widget+"Id"這個屬性,那麼新生成的VM就是用它做爲它的$id
下面是一個完整的實例用於教導你如何定義使用一個UI。
模塊間通訊及屬性監控 $watch,$fire, $unwatch
avalon內置了一個強大的自定義事件系統,它在綁定在每個VM上。每個VM都擁有$watch, $unwatch, $fire這三個方法,及一個$events對象。$events是用於儲存各類回調。先從單個VM提及,若是一個VM擁有aaa這個屬性,若是咱們在VM經過$watch對它監控,那麼當aaa改變值時,它對應的回調就會被觸發!
var vmodel = avalon.define( "test" , function (vm){ |
vm.$watch( "aaa" , function (newValue, oldValue){ |
注意,它只能監聽當前屬性的變更。
咱們還能夠經過$unwatch方法,移除對應的回調。若是傳入兩個參數,第一個是屬性名,第二個是回調,那麼只移除此回調;若是隻傳入一個屬性名,那麼此屬性關聯的全部回調都會被移除掉。
有時,咱們還綁定了一些與屬性名無關的事件回調,想觸發它,那隻能使用$fire方法了。$fire方法第一個參數爲屬性名(自定義事件名),其餘參數隨意。
var vmodel = avalon.define( "test" , function (vm){ |
vm.$watch( "cat" , function (){ |
avalon.log(avalon.slice(arguments)) |
vmodel.$fire( "cat" ,1,2,3) |
更高級的玩法,有時咱們想在任何屬性變化時都觸發某一個回調,這時咱們就須要$watch一個特殊的屬性了——「$all」。不一樣的是,$watch回調的參數多了一個屬性名,排在最前面。
var vmodel = avalon.define( "test" , function (vm){ |
vm.$watch( "$all" , function (){ |
avalon.log(avalon.slice(arguments)) |
手動觸發$fire是位隨着高風險的,框架內部是作了處理(只有先後值發生變化纔會觸發),所以萬不得已使用它,但又爆發死循環怎麼辦?這樣就須要暫時中斷VM的屬性監控機制。使用$unwatch(),它裏面什麼也不傳,就暫時將監控凍結了。恢復它也很簡單,使用$watch(),裏面也什麼也不傳!
不過最強大的用法是實現模塊間的通訊(由於在實際項目中,一個頁面可能很是大,有多少人分塊製做,每一個人本身寫本身的VM,這時就須要經過某種機制來進行數據與方法的聯動了),這是使用$fire方法達成的。只要在$fire的自定義事件名前添加"up!", "down!", "all!"前綴,它就能實現angular類似的$emit,$broadcast功能。
< meta http-equiv = "Content-Type" content = "text/html; charset=UTF-8" > |
< script src = "avalon.js" ></ script > |
avalon.define("ancestor", function(vm) { |
vm.$watch("aaa", function(v) { |
avalon.log("ancestor.aaa事件被觸發了") |
vm.$fire("capture!aaa", "capture") |
avalon.define("parent", function(vm) { |
vm.$watch("aaa", function(v) { |
avalon.log("parent.aaa事件被觸發了") |
vm.$fire("all!aaa", "broadcast") |
avalon.define("son", function(vm) { |
vm.$watch("aaa", function(v) { |
avalon.log("son.aaa事件被觸發了") |
vm.$fire("bubble!aaa", "bubble") |
< body class = "ms-controller" ms-controller = "ancestor" > |
< h3 >avalon vm.$fire的升級版 </ h3 > |
< button type = "button" ms-click = "click" > |
< div ms-controller = "parent" > |
< button type = "button" ms-click = "click" >broadcast</ button > |
< div ms-controller = "son" > |
< button type = "button" ms-click = "click" > |
過濾器
avalon從angular中抄來管道符風格的過濾器,但有點不同。 它只能用於{{}}插值表達式。若是不存在參數,要求直接跟|filter,若是存在參傳,則要用小括號括起,參數要有逗號,這與通常的函數調用差很少,如|truncate(20,"……")
avalon自帶如下幾個過濾器
-
html
-
沒有傳參,用於將文本綁定轉換爲HTML綁定
-
uppercase
-
大寫化
-
lowercase
-
小寫化
-
truncate
-
對長字符串進行截短,truncate(number, truncation), number默認爲30,truncation爲「...」
-
camelize
-
駝峯化處理
-
escape
-
對相似於HTML格式的字符串進行轉義,把尖括號轉換爲> <
-
currency
-
對數字添加貨幣符號,以及千位符, currency(symbol)
-
number
-
對數字進行各類格式化,這與與PHP的number_format徹底兼容, number(decimals, dec_point, thousands_sep),
decimals 可選,規定多少個小數位。
dec_point 可選,規定用做小數點的字符串(默認爲 . )。
thousands_sep 可選,規定用做千位分隔符的字符串(默認爲 , ),若是設置了該參數,那麼全部其餘參數都是必需的。
-
date
-
對日期進行格式化,date(formats)
'yyyy': 4 digit representation of year (e.g. AD 1 => 0001, AD 2010 => 2010) |
'yy': 2 digit representation of year, padded (00-99). (e.g. AD 2001 => 01, AD 2010 => 10) |
'y': 1 digit representation of year, e.g. (AD 1 => 1, AD 199 => 199) |
'MMMM': Month in year (January-December) |
'MMM': Month in year (Jan-Dec) |
'MM': Month in year, padded (01-12) |
'M': Month in year (1-12) |
'dd': Day in month, padded (01-31) |
'EEEE': Day in Week,(Sunday-Saturday) |
'EEE': Day in Week, (Sun-Sat) |
'HH': Hour in day, padded (00-23) |
'hh': Hour in am/pm, padded (01-12) |
'h': Hour in am/pm, (1-12) |
'mm': Minute in hour, padded (00-59) |
'm': Minute in hour (0-59) |
'ss': Second in minute, padded (00-59) |
's': Second in minute (0-59) |
'Z': 4 digit (+sign) representation of the timezone offset (-1200-+1200) |
format string can also be one of the following predefined localizable formats: |
'medium': equivalent to 'MMM d, y h:mm:ss a' for en_US locale (e.g. Sep 3, 2010 12:05:08 pm) |
'short': equivalent to 'M/d/yy h:mm a' for en_US locale (e.g. 9/3/10 12:05 pm) |
'fullDate': equivalent to 'EEEE, MMMM d,y' for en_US locale (e.g. Friday, September 3, 2010) |
'longDate': equivalent to 'MMMM d, y' for en_US locale (e.g. September 3, 2010 |
'mediumDate': equivalent to 'MMM d, y' for en_US locale (e.g. Sep 3, 2010) |
'shortDate': equivalent to 'M/d/yy' for en_US locale (e.g. 9/3/10) |
'mediumTime': equivalent to 'h:mm:ss a' for en_US locale (e.g. 12:05:08 pm) |
'shortTime': equivalent to 'h:mm a' for en_US locale (e.g. 12:05 pm) |
例子:
生成於{{ 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可傳的格式類型很是多,但不是全部瀏覽器都支持這麼多,詳看這裏
多個過濾器一塊兒工做
< div >{{ prop | filter1 | filter2 | filter3(args, args2) | filter4(args)}}</ div > |
若是想自定義過濾器,能夠這樣作
avalon.filters.myfilter = function (str, args, args2){ |
AMD 加載器
avalon裝備了AMD模範的加載器,這涉及到兩個全局方法 require與define
require(deps, callback)
deps 必需。String|Array。依賴列表,能夠是具體路徑或模塊標識,若是想用字符串表示多個模塊,則請用「,」隔開它們。
callback 必需。Function。回調,當用戶指定的依賴以及這些依賴的依賴樹都加載執行完畢後,纔會安全執行它。
若是想禁止使用avalon自帶的加載器,能夠在第一次調用require方法以前,執行以下代碼:
<meta http-equiv= "Content-Type" content= "text/html; charset=UTF-8" > |
<script src= "require.js" ></script> |
<script src= "avalon.mobile.js" ></script> |
avalon.config({loader: false }) |
avalon.define( "xxx" , function (vm){ |
<body ms-controller= "xxx" > |
與jquery更好的集成,好比一些舊系統,直接在頁面引入jquery庫與其大量jquery插件,改爲動態加載方式成本很是大。怎麼樣才能與jquery和平共存,亦能讓AMD加載發揮做呢?先引入jquery庫, 而後將avalon.modules.jquery 加個預設值(exports: jquery用於shim機制, state: 2 代表它已經加載完畢)就好了。
若是你想用其餘AMD加載器,最好的辦法仍是建議直接打開源碼,拉到最底幾行,把加載器禁用了!
define方法用於定義一個模塊,格式爲:
define( id?, deps?, factory )
id
可選。String。模塊ID。它最終會轉換一個URL,放於 $.modules中。
deps
可選。String|Array。依賴列表。
factory
必需。Function|Object。模塊工廠。它的參數列參爲其依賴模塊全部返回的值,若是某個模塊沒有返回值,則對應位置爲undefined
注意, define方法不能寫在script標籤的innerHTML中,只能寫在JS文件裏。
avalon與seajs, https://github.com/RubyLouvre/avalon/issues/313
咱們也能夠在源碼裏面直接移除AMD加載器模塊。
路由系統
它須要依賴於另外一個獨立的組件mmRouter,用法請見這裏
AJAX
AJAX可使用jQuery或mmRequest, mmRequest體積更少,覆蓋jQuery ajax模塊的90%功能,而且在現代瀏覽器中使用了XMLHttpRequest2實現,性能更佳。
經過AJAX加載新數據到已存在的VM中
data: JSON.parse(JSON.stringify(vm.$model)), |
success: function (ajaxData) { |
ajaxData = filterData(ajaxData) |
var newData = avalon.mix( true , {}, vm.$model, ajaxData) |
if (vm.hasOwnProperty(i) && i !== "hasOwnProperty" ){ |
提交VM中的數據到後臺,要當心死循環,詳見這裏
文件上傳要用mmRequest的upload方法
擴展功能
avalon如今有三個擴展點,一是在avalon.fn上添加新的原型方法,這是用於處理DOM的,二是在avalon.bindingHandlers與 avalon.bindingExecutors上添加新的綁定(ms-xxx),三是在avalon.filters添加新的過濾器。
添加原型方法就不用多說,建議儘量返回this,實現鏈式操做,this[0]爲它包含的元素節點。
添加過濾器也很簡,翻看源碼看看lowercase如何實現就好了。
添加新綁定難一點,框架bindingHandlers要求對應的處理函數有兩個參數,data與vmodels, data擁有以下幾個屬性:
- element: 綁定了ms-xxx的元素,如<div ms-xxx-yyy='zzz'>innerHTML</div>,ms-xxx綁定所在的DIV元素。
- value:是指mx-xxx綁定的這個特性節點的值,即上面的zzz。
- param:是指mx-xxx綁定名以「-」分開幾截,除了最前面的兩部分外的東西,如這裏的「yyy」。
vmodels是指,從DOM樹最頂點到添加此綁定的元素所路過的ms-controller的值(它們都對應一個VM)。注意,ms-each, ms-with也產生VM。
bindingHandlers裏的函數用於初始化綁定,它會對綁定屬性作一些分解,放進parseExprProxy中,parseExprProxy會再調用parseExpr,將它轉換爲求值函數,放進行對應VM屬性的subscribers數組內(操做方爲registerSubscriber)。
bindingExecutors裏的的函數爲真正的視圖刷新函數,每當VM發生改變後,都會被執行(操做方爲notifySubscribers)。
如今avalon擁有如此多綁定:
在IE6下調試avalon
因爲IE6下沒有console.log,若是又不想用VS等巨無霸IDE,能夠本身定義如下方法
console.log = function (str){ |
avalon.ready( function () { |
var div = document.createElement( "pre" ); |
div.className = "mass_sys_log" ; |
div.innerHTML = str + "" ; |
document.body.appendChild(div); |
上線後,將.mass_sys_log{ display: none; }
若是是高級瀏覽器,avalon會在控制檯上打印許多調試消息,若是不想看到它們,能夠這樣屏蔽它們:avalon.config({debug: false})
權限控制
將頁面模塊化,大量使用ms-include-src,沒有權限就返回空頁面,權限夠了,但不是最高級,那它返回的模板文件也不同/p>
更多學習資料
利用avalon 實現一個簡單的成績單, 教你如何使用ms-each數組循環綁定與$watch回調
若是您以爲此文有幫助,能夠打賞點錢給我支付寶1669866773@qq.com ,或掃描二維碼