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=