avalonJS是司徒正美開發(fā)和維護(hù)的前端mvvm框架,可以輕松實現(xiàn)數(shù)據(jù)的隔離和雙向綁定,,相比angularJS等前端框架它有如下優(yōu)勢: 1.壓縮后僅有60多kb,,而angular的min版是2MB左右(無視其gzip版),; 2.兼容IE6+,符合天朝市場需求; 3.效率更高,,跑起來比angular和knockout都要更快,在移動端上該優(yōu)勢會更大(avalon有移動端專版的avalon.modern.js),。關(guān)于其性能更詳細(xì)的介紹可以看 這里 ,; 4.涵蓋了angular的大部分功能,且實現(xiàn)方式更為便捷,、上手更容易; 5.有配套的UI庫(當(dāng)然這個按需選擇即可),,由司徒正美及其“去哪兒”團(tuán)隊維護(hù),有相關(guān)的中文文檔(下方會提到),,除了在github提交issue,,你也可以加入正美的Q群79641290 來交流問題或提交bug。 (這位兄臺,,谷歌送溫暖,,開門查水表) 然而avalon也有自己的劣勢——知名度較低。不過畢竟國產(chǎn)的東西沒經(jīng)BAT推廣,,要像seaJS那樣馳名中外倒是不容易,。
相關(guān)中文文檔 正美其實私下寫了不少avalon的官方api和教程,大家可以訪問如下地址: GitHub (下載最新的avalon以及實例(examples文件夾里),,通過實例來掌握某些功能的實現(xiàn)是很好的學(xué)習(xí)途徑) Avalon快速入門 (比較快捷的入門課程,,只用了幾篇文章來介紹了最常用的一些功能) API文章(正美的博文,篇幅較大,,涵蓋知識點(diǎn)很多,,可以當(dāng)作API來查閱,只是正美的博客排版真的,。,。??雌饋砺猿粤Γ?,也可以在 這里 查看更規(guī)范的API。 Avalon亂燉 (強(qiáng)烈推薦,,用了20多篇文章較詳細(xì)地,、漸進(jìn)地介紹avalon,,必讀的就是啦)
本系列初衷 雖然正美已經(jīng)細(xì)心編寫了不少中文文檔,不過有的文章技術(shù)門檻有點(diǎn)高,,不太適合初學(xué)者,,另外作為avalon的用戶,以用戶的角度來較詳細(xì)地介紹avalon或許會更合適些,。 本系列相比正美的教程,,會更側(cè)重于“怎么用”,而非其機(jī)制或原理的介紹,。 另外也希望本系列能為推廣avalon出一份綿薄之力,,希望能讓更多的前端愛好者開始接觸avalon,并喜歡上這個前端利器,。
本系列技術(shù)需求 本系列除了avalonJS之外,,還會搭配requireJS做輔助,特別是后面我們會使用avalon的路由系統(tǒng),,來做一個單頁面站點(diǎn)(放到移動端就是SPA了),,需要requireJS及其插件來按需加載腳本和樣式文件,故建議查閱本系列的朋友要多多少少會一點(diǎn)requireJS的知識,。 個人還是覺得avalon搭配requireJS的話,,在前端可謂hold住全場了(咱忽略node及其框架...)~
我們可以在 這里 獲取最新版本的avalonJS,然后將其引入頁面中(本章先不考慮搭配requireJS,,僅僅先玩一玩,、介紹下): <!DOCTYPE html> <html> <head lang="en"> <meta charset="UTF-8"> <title>初玩阿瓦隆</title> <script type="text/javascript" src="avalon.js"></script> </head> <body> <div></div> </body> </html> 接著,類似于ng的“ng-controller”,,avalon的控制域?qū)傩悦凶觥癿s-controller”,,你可以把它當(dāng)作一個監(jiān)聽器,把它綁定到一個容器后,,avalon就能掃描和監(jiān)聽這個容器內(nèi)的所有 (綁定了avalon方法或帶有插值表達(dá)式的) 元素了,。 我們來給div加上這個監(jiān)聽器,并在里面寫一個 avalon插值表達(dá)式{{ XXX }} : <div ms-controller="wrap">{{a}}</div> 你現(xiàn)在運(yùn)行它的話,,還沒有任何效果,,因為我們還沒有寫腳本來讓avalon工作起來。我們可以來這么一段簡單的腳本: <body> <div ms-controller="wrap">{{a}}</div> <script type="text/javascript"> var abc = avalon.define({ //abc是隨便起的一個名字,,用作該Model的載體 $id: "wrap", //告訴avalon這個Model是作用于哪個ms-controller的 a: "你好啊" //定義一個avalon對象屬性“a”,,其值是“你好啊” }); avalon.scan(); //要在最后加這一句,讓avalon掃描文檔,,從而執(zhí)行起來 </script> </body> 在avalon中,,我們用 avalon.define({ ... }) 的形式來定義一個Model實例(其參數(shù)可以看做一個avalon數(shù)據(jù)對象),其中的 $id 是內(nèi)置屬性,對應(yīng)所要掃描和監(jiān)控的控制域名,。 我們還在內(nèi)部定義了一個屬性"a",,故在對應(yīng)的控制域 (如這段代碼對應(yīng)的域是綁定ms-controller="wrap"的div標(biāo)簽) 里,我們使用avalon插值表達(dá)式{{a}}的話,,可以自動綁定其值“你好啊”,。 上述代碼運(yùn)行效果如下:
當(dāng)然,avalon有著更類似ng的寫法: avalon.define("wrap", function(vm) {
......
})
但個人覺得沒必要,,還是下方的寫法來的簡單(本系列后續(xù)的實例也將遵從該寫法): var vm = avalon.define({
$id:wrap,
......
})
下面這段代碼可以幫你更好了解avalon的控制域: <body> <div ms-controller="wrap">{{a}}</div> <div ms-controller="wrap2"> {{a}} <span>{,}</span> </div> <script type="text/javascript"> var abc = avalon.define({ $id: "wrap", a: "你好啊" }); var def = avalon.define({ $id: "wrap2", a: "大家好", b: "哈哈哈" }); avalon.scan(); </script> </body> 執(zhí)行效果如下:
兩個作用域(ms-controller)之間可以互相訪問彼此的數(shù)據(jù),,還記得我們給avalon.define的前面定義了一個載體么(varXX= avalon.define),,利用它就能輕松獲取: <body> <div ms-controller="wrap">{{a}}</div> <div ms-controller="wrap2"> {{a}} <span>{,}</span> </div> <script type="text/javascript"> var abc = avalon.define({ $id: "wrap", a: "你好啊" }); var def = avalon.define({ $id: "wrap2", a: "大家好", b: abc.a //獲取第一個Model里的屬性值 }); avalon.scan(); </script> </body> 執(zhí)行效果:
數(shù)據(jù)和視圖同步 上方我們實現(xiàn)了非常簡單的數(shù)據(jù)綁定,,將一個avalon對象屬性a綁定到DOM元素上。不過avalon更有趣和實用的地方是它實現(xiàn)了數(shù)據(jù)與視圖的同步,,說的簡單點(diǎn),,我們用腳本修改了a的值,那么DOM上綁定的數(shù)據(jù)也會跟著改變(當(dāng)然反過來也是一樣的): <body> <div ms-controller="wrap"> <span>{{a}}</span> <input ms-duplex="a" /> </div> <script type="text/javascript"> var abc = avalon.define({ $id: "wrap", a: "你好啊" }); avalon.scan(); </script> </body> 注意我們這里增加了一個 <inputms-duplex="a"/> ,,其中的 ms-duplex 是avalon的雙工綁定屬性,,它除了負(fù)責(zé)將VM中對應(yīng)的值(如本例是a)放到表單元素的value中,還對元素偷偷綁定一些事件,,用于監(jiān)聽用戶的輸入從而自動刷新VM,。 執(zhí)行如下:
實例 利用avalon數(shù)據(jù)-視圖同步的特性,我們可以更便捷地,、更少代碼地實現(xiàn)某些功能,。舉個例子,我們來實現(xiàn)一個選項卡的功能:
如上圖的選項卡你會如何實現(xiàn)呢,?可能你會寫兩個ul來對應(yīng)下方兩個選項卡列表,,每個ul里都寫上4個li(或者讓后端人員通過后端框架來寫loop,從而動態(tài)生成li),,然后你再把第二個ul隱藏了,,接著寫個方法,讓鼠標(biāo)移到第二個選項卡標(biāo)題時,,第一個ul隱藏,,第二個ul顯示,對吧,。 還有右上角的 “更多XX” 的連接,,也可以通過隱藏-顯示的方式來實現(xiàn) 你的DOM代碼可能是這樣的: <div> <span id="gg">公告</span><span id="bd">媒體報道</span> <a id="more_gg" href="#!/gg">更多公告</a><a id="more_bd" href="#!/bd">更多報道</a> <ul id="gg_list"> <li><a href="#!/gg/1" title="公告文章標(biāo)題1">公告文章標(biāo)題1</a></li> <li><a href="#!/gg/2" title="公告文章標(biāo)題2">公告文章標(biāo)題2</a></li> <li><a href="#!/gg/3" title="公告文章標(biāo)題3">公告文章標(biāo)題3</a></li> <li><a href="#!/gg/4" title="公告文章標(biāo)題4">公告文章標(biāo)題4</a></li> </ul> <ul id="bd_list"> <li><a href="#!/bd/1" title="媒體報道文章標(biāo)題1">媒體報道文章標(biāo)題1</a></li> <li><a href="#!/bd/2" title="媒體報道文章標(biāo)題2">媒體報道文章標(biāo)題2</a></li> <li><a href="#!/bd/3" title="媒體報道文章標(biāo)題3">媒體報道文章標(biāo)題3</a></li> <li><a href="#!/bd/4" title="媒體報道文章標(biāo)題4">媒體報道文章標(biāo)題4</a></li> </ul> </div> 但使用avalon的話,一切都更簡單: <div ms-controller="list"> <span ms-mouseover="changeUl(gg)">公告</span> <span ms-mouseover="changeUL(bd)">媒體報道</span> <a ms-href="'#!/'+ more_name">{{more_text}}</a> <ul> <li ms-repeat="infoList"> <a ms-href="'#!/'+ more_name + '/' + el.id" ms-title="el.title">{{el.title}}</a> </li> </ul> </div> 首先它只有一個“更多XX”的<a>標(biāo)簽,,而且只有一個<ul>,,而且不存在任何后端標(biāo)簽的介入就能實現(xiàn)數(shù)據(jù)循環(huán)綁定,。 它的優(yōu)勢在數(shù)據(jù)越多的時候會越明顯(例如每個ul要顯示100條li)。 我們來看看avalon的腳本應(yīng)當(dāng)怎么寫: <body> <script type="text/javascript"> var gg=[{"id":"1","title":"公告文章標(biāo)題1"},{"id":"2","title":"公告文章標(biāo)題2"},{"id":"3","title":"公告文章標(biāo)題3"},{"id":"4","title":"公告文章標(biāo)題4"}]; var bd=[{"id":"1","title":"媒體報道文章標(biāo)題1"},{"id":"2","title":"媒體報道文章標(biāo)題2"},{"id":"3","title":"媒體報道文章標(biāo)題3"},{"id":"4","title":"媒體報道文章標(biāo)題4"}]; </script> <div ms-controller="list"> <span ms-mouseover="changeUl(1)">公告</span> <span ms-mouseover="changeUl(0)">媒體報道</span> <a ms-href="'#!/'+ more_name">{{more_text}}</a> <ul> <li ms-repeat="infoList"> <a ms-href="'#!/'+ more_name + '/' + el.id" ms-title="el.title">{{el.title}}</a> </li> </ul> </div> <script type="text/javascript"> var vm = avalon.define({ $id: "list", more_name: "gg", more_text: "更多公告", gg:gg, bd:bd, infoList:gg, changeUl:function(flag){ if(flag){ //鼠標(biāo)移過“公告”選項卡頭部 vm.more_name = "gg"; vm.more_text = "更多公告"; vm.infoList = vm.gg; }else{ //鼠標(biāo)移過“媒體報道”選項卡頭部 vm.more_name = "bd"; vm.more_text = "更多報道"; vm.infoList = vm.bd; } } }); avalon.scan(); </script> </body> 執(zhí)行效果:
我們來逐步分析下上方的代碼,。首先看第一段腳本: <script type="text/javascript"> var gg=[{"id":"1","title":"公告文章標(biāo)題1"},{"id":"2","title":"公告文章標(biāo)題2"},{"id":"3","title":"公告文章標(biāo)題3"},{"id":"4","title":"公告文章標(biāo)題4"}]; var bd=[{"id":"1","title":"媒體報道文章標(biāo)題1"},{"id":"2","title":"媒體報道文章標(biāo)題2"},{"id":"3","title":"媒體報道文章標(biāo)題3"},{"id":"4","title":"媒體報道文章標(biāo)題4"}]; </script> 這里的 gg 表示“公告”的列表JSON數(shù)據(jù),,bd 則是“媒體報道”的列表JSON數(shù)據(jù),你可以讓后端的朋友直接在此處提供JSON數(shù)據(jù)過來,。我們后續(xù)會利用avalon把這些數(shù)據(jù)綁定到頁面視圖上,。 我們再看DOM結(jié)構(gòu): <div ms-controller="list"> <span ms-mouseover="changeUl(1)">公告</span> <span ms-mouseover="changeUl(0)">媒體報道</span> <a ms-href="'#!/'+ more_name">{{more_text}}</a> <ul> <li ms-repeat="infoList"> <a ms-href="'#!/'+ more_name + '/' + el.id" ms-title="el.title">{{el.title}}</a> </li> </ul> </div> <span>中的 ms-mouseover 是avalon的“onmouseover”方法,其值 changeUl(X) 是我們在最后的avalon腳本中定義的一個事件方法,,然后比如當(dāng)鼠標(biāo)移到“媒體報道”的span上,,會觸發(fā)綁定是 changeUl(0) 事件。 我們再看看 <ams-href="'#!/'+ more_name">{{more_text}}</a> 這里的 ms-href 自然也是avalon中的“href”屬性,,可以植入avalon對象屬性(如這里的more_name),,也可以加上字符串(如這里的'#!/'),但要用引號括起來,,不然會被當(dāng)作avalon對象屬性處理,。 接著是最重要的部分: <li ms-repeat="infoList"> <a ms-href="'#!/'+ more_name + '/' + el.id" ms-title="el.title">{{el.title}}</a> </li> 我們使用了 ms-repeat="XX" 屬性來綁定要重復(fù)顯示的哈希數(shù)據(jù),同時會生成一個代理VM對象,,該代理對象擁有el,,$index, $first, $last, $remove 等屬性(點(diǎn)這里查看詳細(xì)),其中我們用到的 el 表示指向當(dāng)前的數(shù)據(jù)元素,,從而可以通過 el.id ,,el.title 來獲取infoList數(shù)組對象的具體元素。 最后咱再看看avalon腳本: <script type="text/javascript"> var vm = avalon.define({ $id: "list", more_name: "gg", more_text: "更多公告", gg:gg, //獲取公告JSON數(shù)據(jù) bd:bd, //獲取媒體報道JSON數(shù)據(jù) infoList:gg, //infoList缺省值為公告JSON數(shù)據(jù) changeUl:function(flag){ if(flag){ //鼠標(biāo)移過“公告”選項卡頭部 vm.more_name = "gg"; vm.more_text = "更多公告"; vm.infoList = vm.gg; //infoList變?yōu)楣鍶SON數(shù)據(jù) }else{ //鼠標(biāo)移過“媒體報道”選項卡頭部 vm.more_name = "bd"; vm.more_text = "更多報道"; vm.infoList = vm.bd; //infoList變?yōu)槊襟w報道JSON數(shù)據(jù) } } }); avalon.scan(); </script> 這里要注意的是我們用了 gg:gg, //獲取公告JSON數(shù)據(jù) bd:bd, //獲取媒體報道JSON數(shù)據(jù) 來獲取和存儲“公告/媒體報道”的JSON數(shù)據(jù)到avalon對象的屬性中(左側(cè)的gg和bd是avalon對象屬性,,右側(cè)的gg和bd是全局變量),,這樣做的原因是后續(xù)的回調(diào)事件changeUl(flag)要通過參數(shù)來判斷和修改vm.infoList的值,而其值應(yīng)同為avalon對象屬性,。如果把代碼改為這樣會出錯(剛用avalon的朋友可能就會這樣寫): <script type="text/javascript"> var vm = avalon.define({ $id: "list", more_name: "gg", more_text: "更多公告", infoList:gg, //infoList缺省值為公告JSON數(shù)據(jù) changeUl:function(flag){ if(flag){ //鼠標(biāo)移過“公告”選項卡頭部 vm.more_name = "gg"; vm.more_text = "更多公告"; vm.infoList = gg; }else{ //鼠標(biāo)移過“媒體報道”選項卡頭部 vm.more_name = "bd"; vm.more_text = "更多報道"; vm.infoList = bd; } } }); avalon.scan(); </script> 執(zhí)行效果如下:
是的,,鼠標(biāo)第一次移上去的時候是無誤的,但再移到其它選項卡的時候就不按常理出牌了,,這是為什么,? 我個人認(rèn)為,在我們第一次獲取全局變量之后,,avalon就會把該變量變?yōu)橐粋€avalon對象,,導(dǎo)致該變量的值就這么被改變了,你可以這樣來調(diào)試: var vm = avalon.define({ $id: "list", more_name: "gg", more_text: "更多公告", gg:gg, bd:bd, infoList:gg, changeUl:function(flag){ console.table(gg); //console出“公告”變量的數(shù)據(jù)信息 if(flag){ vm.more_name = "gg"; vm.more_text = "更多公告"; vm.infoList = vm.gg; }else{ vm.more_name = "bd"; vm.more_text = "更多報道"; vm.infoList = vm.bd; } } }); avalon.scan(); 結(jié)果(第一次回調(diào)事件的gg是正常的,,但第二次開始就改變了):
因此我們要記得,,若存在外部引入的數(shù)據(jù),應(yīng)用一個avalon對象屬性保存起來。
第一篇入門文章就到這里,,下一篇我們開始以配合requireJS的形式來使用avalon,,共勉~ |
|