迷你MVVM框架 avalonjs1.5 入门教程

avalon经过几年以后,已成为国内一个举足轻重的框架。它提供了多种不同的版本,满足不同人群的需要。比如avalon.js支持IE6等老旧浏览器,让许多靠政府项目或对兼容性要求够高的公司也能享受MVVM的乐趣。avalon.modern.js支持IE10以上版本,优先使用新API,性能更优,体积更少。avalon.mobile.js在avalon.modern的基础提供了触屏事件的支持,满足大家在移动开发的需求。此外,它们分别存在avalon.xxx.shim版本,指无自带加载器版,avalon.xxx.min版本,指上线压缩版本。

avalon早期严重受到angular与knockout的影响,API与它们很相近,经过多年的发展,渐渐摸索出自己一套模式。avalon1.5是一个里程碑的版本,它带来许多全新的特性,让我们编写代码更加爽快。

avalon1.5的下载地址: https://github.com/RubyLouvre/avalon/tree/1.5

  1. 视图模型
  2. 非监控属性与监控属性
  3. 视图模型的作用域
  4. 扫描机制
  5. 指令(绑定)
  6. 数据填充(ms-text, ms-html)
  7. 模板绑定(ms-include)
  8. 类名切换(ms-class, ms-hover, ms-active)
  9. 事件绑定(ms-on,……)
  10. 显示绑定(ms-visible)
  11. 插入绑定(ms-if)
  12. 双工绑定(ms-duplex)
  13. 样式绑定(ms-css)
  14. 数据绑定(ms-data)
  15. 属性绑定(ms-attr)
  16. 循环绑定(ms-repeat,ms-each,ms-with)
  17. 动画绑定(ms-effect)
  18. 自定义标签组件
  19. 模块间通信及属性监控 $watch,$fire
  20. 过滤器
  21. 自定义指令(绑定)
  22. 加载器
  23. AJAX
  24. 路由系统
  25. 在IE6下调试avalon
  26. 其他要注意的地方(更新VM等)

视图模型

avalon与jQuery最大的一个区别是,思维的不同。jQuery要操作一个元素,总是设法找到此元素,想象这个元素是否有ID,有某个类名,存在某个特定的标签下,是父节点的第几个孩子,诸如此类,最后拼凑出一个CSS表达式,然后$(expr)找到元素,然后再进行操作,于是JS代码里满屏$。维护代码的人,总是要对着页面来看看,这表达式是对应某某元素,如果只有ID,类名还好,新手很是写出很长的CSS表达式,导致你最后崩溃掉。

avalon要操作某个元素,就直接在HTML为它添加一些指令,这些指令或者以ms-开头的元素属性,或是标签之间的4个花括号。指令里面存在某些变量,这些变量最后在JS聚集成一个对象,这就叫做VM( View Model, 视图模型 )。我们只要操作这个VM的数据变动就行了,页面上就会自动变化。有了这一层的分离,我们在代码量就少能许多操作DOM的代码,专致于业务本身。比如说:

<p>{{aaa}}</p>

相当于jQuery的以下代码:

$(function(){
   $("p").text(aaa)
})

那我们看看怎么定义一个VM吧。avalon在1.5之前存在两种定义方式,现在1.5只支持新风格,即

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属性,它是用于指定其在页面的作用范围。

avalon.define会返回一个新对象,它除了之前我们定的属性与方法,还添加了$watch, $events, $fire, $model等属性与方法。

当我们以vm.a = 4来重新赋值时,页面上用到a的地方会自然作出反应,这个行为称之为 绑定 ,有的属性会使用ms-duplex指令绑定到表单元素上,这时反应是双向的,input,select, textarea的值被用户改动时,会自然反应到VM上,而我们对VM上的操作也会反应到表单元素上,这叫做 双工绑定

有的东西,你压底只有它只作用一次,如大表格的数据展示,以后没有任何互动交互,那我们有几种方式:

  • 将该属性的名字以$开头,以$aaa,这样标识它为非监控属性。
  • 将该属性的名字放到$skipArray数组,也能标识它非监控属性,因为有的名字需要前后一致,后端不愿意加$,这种方式更灵活。
  • 有的属性我想它在这里可以多次改动,有的则显示就不改了,可以使用 单向绑定,需要在ms-*属性的值前面加::,或花括号内部的前面加::
<!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类:

  • 当其值为字符串,数值,布尔,undefined, null等简单数据类型时,为 监控属性
  • 当它定义在$computed对象中时,其值为一个对象,拥有get,set方法,为 计算属性 。计算属性总是有一个或一个以上的监控属性构成。
  • 当其值的类型为数组时,称之为 监控数组 ,我们可以改动它的方法,来同步视图。在1.5中,我们不断可以监听其长度变化,还可以监听其元素或元素的属性变化。
  • 当其值为一个对象时,它里面的对象也继续转换为计算属性,监控属性,这个对象我们称之为 子VM

在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> 

更详细的内容可见 新官网

类名切换(ms-class, ms-hover, ms-active)

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>   

转载于:https://www.cnblogs.com/cymbidium/p/5341414.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值