前端框架Avalon:简介
2016-09-19 19:37
309 查看
avalon 2
迷你 、 易用 、 高性能 的前端MVVM框架测试页面avalon仓库perf目录下的
index.html,
index.*.html文件
简介
avalon2是一款基于虚拟DOM与属性劫持的 迷你、 易用、 高性能 的 前端MVVM框架, 拥有超优秀的兼容性, 支持移动开发, 后端渲染, WEB Component式组件开发, 无需编译, 开箱即用。谁在使用avalon
使用方式
avalon2: https://github.com/RubyLouvre/avalon/tree/2.1.5/distCDN: https://cdnjs.com/libraries/avalon.js (推荐)
CDN: http://www.bootcdn.cn/avalon.js/
注意, 不要使用avalon2.0s,avalon2.0b1,avalon2.0b2,那是很早期的beta版本
npm install avalon2
下面回来,你发现有一个dist目录,然后使用script标签引用当中的avalon.js或avalon.modern.js文件
avalon2是使用一份源码编译成N个版本:
avalon 支持IE6+及古老的W3C浏览器(判定标准是 这些浏览器是否支持VBScript, __defineSetter__, __defineGetter__) avalon.modern 支持IE10+及较新的W3C浏览器(判定标准是 这些浏览器是否支持Object.defineProperty, addEventListener) avalon.next 支持IE12+(edge)及chrome49, firefox49(判定标准是 这些浏览器是否支持Proxy, document.registerElement)
学习资料
avalon2-seed 这是一个avalon项目的框架,可以将它作为avalon项目的工程初始化目录配置。webpack+avalon的学习教程
1.4的入门教程
1.4的仓库地
1.4的另一个更漂亮的教程
1.5的入门教程
1.5的仓库地址
1.×的视频教程
1.*的官网地址
前端乱炖网站上的avalon1.5学习教程
segementfault网站上的avalon2学习教程
avalon论坛
QQ学习群: 314247255
电子书下载:avalon cookbook
入门例子
<!DOCTYPE html> <html> <head> <title>first example</title> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <script src="./dist/avalon.js"></script> <script> var vm = avalon.define({ $id: "test", name: "司徒正美", array: [11,22,33] }) setTimeout(function(){ vm.array.set(0, 444) }, 3000) </script> </head> <body ms-controller="test"> <input ms-duplex="@name"> <p>Hello,{{@name}}!</p> <ul> <li ms-for="($index,el) in @array">{{$index}}--{{el}}</li> </ul> </body> </html>
这里面涉及一些知识点
vm,使用avalon.define方法生成,必须带$id属性
指令,以ms-开头的属性及双花括号的插值表达式
圈定作用域,使用ms-controller告诉框架,只处理这个范围内的标签
引导符,使用
@或
##来告诉框架这些变量是来自vm的
自动扫描机制
avalon2.1.15后还可以使用
:xxxx短指令.
<body :controller="test"> <input :duplex="@name"> <p>Hello,{{@name}}!</p> <ul> <li :for="($index,el) in @array">{{$index}}--{{el}}</li> </ul> </body>
后端渲染
https://github.com/RubyLouvre/avalon-server-render-exampleavalon起源
avalon 是一个简单易用迷你的MVVM框架,它最早发布于2012.09.15, 为解决同一业务逻辑存在各种视图呈现而开发出来的。 事实上,这问题其实也可以简单地利用一般的前端模板加jQuery 事件委托 搞定, 但随着业务的膨胀, 代码就充满了各种选择器与事件回调,难以维护。 因此彻底的将业务与逻辑分离,就只能求助于架构。 最初想到的是MVC,尝试过backbone,但代码不降反升,很偶尔的机会,碰上微软的WPF, 优雅的MVVM架构立马吸引住我,我觉得这就是我一直寻找的解决之道。avalon将所有前端代码彻底分成两部分,视图的处理通过绑定实现(angular有个更炫酷的名词叫指令), 业务逻辑则集中在一个个叫VM的对象中处理。我们只要操作VM的数据,它就自然而然地神奇地同步到视图。 显然所有神秘都有其内幕,C#是通过一种叫访问器属性的语句实现,那么JS也有对应的东西。 感谢上帝,IE8最早引入这东西(
Object.defineProperty),可惜有BUG,但带动了其他浏览器实现它, IE9+便能安全使用它。 对于老式IE,我找了好久,实在没有办法,使用VBScript实现了。
Object.defineProperty或VBS的作用是将对象的某一个属性,转换一个setter与getter, 我们只要劫持这两个方法,通过Pub/Sub模式就能偷偷操作视图。为了纪念WPF的指引, 我将此项目以WPF最初的开发代号avalon来命名。 它真的能让前端人员脱离DOM的苦海,来到数据的乐园中!
avalon的所有指令都是以
ms-*命名,ms是用纪念我之前的一个框架
mass Framework!
谁在使用avalon
欢迎各位使用者到QQ群找作者提交你们公司的LOGO与链接vm
avalon的所有操作都是围绕vm进行。 vm,亦即view model,视图模型。只要我将一个JS对象添加一个$id属性, 再放到avalon.define方法里面,就能得到一个vm。var vm = avalon.define({ $id: "start", name: "test" })
vm是一种利用Proxy或 Object.defineProperties或VBScript创建的特殊对象。
里面以$带头的属性或放到$skipArray,都转换为访问器属性,也就是其他语言的setter, getter。因此如果这个属性最初没有定义,那么它就不会转换为访问器属性,修改该属性,就不会刷新视图。
avalon定义了的vm,都可以在avalon.vmodels中查看到。我们可以在chrome控制台下看一下刚才的start vm的构造。
平时而言,vm是一种比较重型的对象。从占用内存角度来划分,浏览器中的四种对象排行如下:
超轻量 Object.create(null)
轻量 一般的对象 {}
重量 带有访问器属性的对象, avalon VM对象
超重量 各种节点或window对象
我们构建VM时只允许存在普通对象(不能是某个函数的实例),函数,数组,数字,字符串,布尔,其他一切不支持(undefined与null不能出现在定义VM时,只能用它们来赋值)
内部属性
VM中以$开头的属性都是框架保留使用的特殊属性,大家为数据起名字时要小心避开这些以$开头的属性,目前除了$id, $events, $watch, $fire, $model比较稳定外, 其他系统属性在不同版本存在增删的情况.
$id, vm的名字
$watch, 用于添加监听函数
$fire, 用于触发监听函数
$events, 用于储存监听函数
$model, 返回一个纯净的JS对象
$hashcode, 2.0新增,由于$id无法保证唯一性,使用这个作为UUID
$accessors, 用于放置访问器的定义,出现在兼容片的avalon VM上.
$render, 2.0新增, 用于生成虚拟DOM树.
$element, 2.0新增, 当我们用ms-controller, ms-important指定一个VM的作用域,对应元素节点会放到这个属性上.
$track, 1.5新增, 用于实现hasOwnProperty方法. VM的hasOwnProperty会对系统属性返回false
另外,avalon不允许在VM定义之后,再追加新属性与方法,比如下面的方式是错误的:
var vm = avalon.define({ $id: "test", test1: "点击测试按钮没反应 绑定失败" }) vm.one = function () { //不能再追加此方法 vm.test1 = "绑定成功" }
但我们可以通过以下方式,实现添加子属性。
var vm = avalon.define({ $id: "test", placehoder: {} }); setTimeout(function () { vm.placehoder = {//我们必须要通过 = ,直接添加一个对象来添加子属性, 不能 aaa: 1, //vm.placehoder.aaa =1; vm.placehoder.bbb = 2这样分散地添加子属性 bbb: 2 } }, 1000)
VM中的数据更新,只能通过 = 赋值方式实现。但要注意在IE6-8,由于VM是一个VBScript对象,为VM添加新属性会抛错, 因此我们想批量更新属性要时格外小心了,需要用hasOwnProperty进行过滤。
注意在IE6-8 下,err是VBscript的关键字,VM中存在这个字段,就会将VM中的其他数组变成字符串,详见这里
为了性能起见,请确保你的对象结构足够扁平,套嵌层次不能太深,里面的数组不能太长。
监控属性
在VM中,改变它们会引起视图改变的属性。如果一个属性是$开头, 或在定义时放在$skipArray数组中,或是函数或节点元素, 它们都不会转换成监控属性.此外, 改变监控属性的值还会触发对应的$watch监听回调.
监控数组
操作此数组的方法会同步视图的特殊数组,它是由VM中的数组自动转换而来。方便与ms-repeat, ms-each配合使用, 能批量同步一大堆DOM节点。监控数组的方法与普通数组没什么不同,它只是被重写了某一部分方法,如 pop, shift, unshift, push, splice,sort, revert。其次添加了四个移除方法,remove, removeAt, removeAll, clear, 及ensure,pushArray,set方法。
pushArray(el), 要求传入一数组,然后将它里面的元素全部添加到当前数组的末端。
remove(el), 要求传入一元素,通过全等于比较进行移除。
removeAt(index), 要求传入一数字,会移除对应位置的元素。
removeAll(arrayOrFunction), 有三种用法,如果是一个函数,则过滤比较后得到真值的元素, 如果是一数组,则将此数组中与原数组相等于的元素全部移除;如果没有任何参数,则全部清空。
clear(),相当于removeAll()的第三种方法,清空数组的所有元素。由于需要同步视图的缘故,不能通过vm.array.length = 0的方法来清空元素。
ensure(el),只有当数组不存在此元素时,才添加此元素。
set(index, el),用于更新某一索引位置中的元素,因为简单数组元素的数组,是不会转换它的元素。
注意,修改某个数组元素必须使用set方法. 如果是修改
对象数组的某个元素的属性可以用
vm.array[1].prop = 'newValue'
<body ms-controller="test"> <div ms-for="el in @arr"> {{el}}<button type="button" ms-click="@arr.remove(el)">点我删除该行</button> </div> <script> avalon.define({ $id: 'test', arr: [1,2,3,4,5,6] }) </script> </body>
非监控属性
这包括框架添加的$id, $events, $model属性, $fire, $watch, $render方法, 及用户自己设置的以$开头的属性,放在$skipArray数组中的属性,值为函数、各种DOM节点的属性, 总之,改变它们的值不会产生同步视图的效果。$watch方法
在avalon早期是, 存在一个对象能mixin进每个VM,让VM具有$watch, $unwatch, $fire, $events等方法或属性. 这有点像jQuery的on, off, trigger方法,只是为了更造近angular等MVVM框架,名字起成这样.此方法是用于监听vm中的对象的属性变化.
换言之,它不能监听函数,不能监听简单数组的元素变化(如[1,2,3]变成[4,2,3])
它能监听子级对象的属性变化,能监听对象数组的属性变化(如[{a:1,b:2}]变成[{a:'change',b:2}]), 还有数组的长度属性变化
此外从1.5起,支持""通配符,解决对数组元素,子属性的监听.注意,号只能出现一次.
下面是$watch方法的的七种用法
var vm = avalon.define({ $id: "test", array: [1, 2, 3], d: 888, arr: [{ a: 1 }, { a: 2 }, { a: 3 }], obj: { a: 1, b: 2 }, a: { b: { c: { d: 33 } } } }) var expect = function (a) { return { to: { be: function (b) { console.log(a == b) } } } } vm.$watch("array.length", function (a, b, name) { console.log('第一组 数组长度', name) }) vm.$watch("arr.*.a", function (a, b, name) { expect(a).to.be(99) expect(b).to.be(1) console.log('第二组 数组元素属性(模糊匹配, 不知道哪个元素变化)', name) }) vm.$watch("obj.a", function (a, b, name) { expect(a).to.be(111) expect(b).to.be(1) console.log('第三组 属性的属性', name) }) vm.$watch("obj.*", function (a, b, name) { expect(a).to.be(111) expect(b).to.be(1) console.log('第四组 属性的属性(模糊匹配)', name) }) vm.$watch("a.b.c.d", function (a, b, name) { expect(a).to.be(88) expect(b).to.be(33) console.log('第五组 属性的属性的属性', name) }) vm.$watch("a.*.c.d", function (a, b, name) { expect(a).to.be(88) expect(b).to.be(33) console.log('第六组 属性的属性的属性(模糊匹配)', name) }) vm.$watch("*", function (a, b, name) { expect(a).to.be(999) expect(b).to.be(888) console.log('第七组 第一层对象的任意属性(模糊匹配)', name) }) setTimeout(function () { vm.array.set(1, 6) vm.array.push(99) vm.arr[0].a = 99 vm.obj.a = 111 vm.a.b.c.d = 88 vm.d = 999 }, 100)
$watch会返回一个函数,用于解除监听:
var unwatch = vm.$watch("aaa", function observe(a, b) { expect(a).to.be(6) expect(b).to.be(2) }) unwatch() //移除当前$watch回调 `
当解除监听后,以后aaa属性的值再发生变化,那么observe方法就不会再执行. 注意unwatch不等于observe
监听函数有三个参数, 第一个是新值, 第二个是旧值, 第三个是发生变动的属性的名字。
$watch方法供与其他操作DOM的库一起使用的,如富文本编辑器什么. 在$watch回调里更新VM自身的属性是非常危险的事,很容易引发死循环
$fire方法
$fire可以传多个参数, 第一个参数为事件名,或者说是VM上已存在的属性名, 当VM中对应的属性发生变化时,框架内部就调用$fire方法, 依次传入属性名,当前属性值,过去属性值。数据模型
是指VM中的$model属性,它是一个纯净的javascript对象,去掉$id, $watch等方法或属性,可以直接通过$.ajax提交给后端,当然我们 还可以通过JSON.parse(JSON.stringify(vm.$model))干掉里面的所有函数。注意,不要修改$model,你只能通过VM来改动$model,否则在1.5中,$model是只读的,每次都是返回一个全新的对象给你 你改了也没有用!
vm是如何作用视图
我们需要在页面上,使用ms-controller或ms-important来圈定每个vm的作用范围。当页面domReady时,vm就将自动将其里面的数据替换到各种指令中去,实现视图刷新效果。注意一个vm只能在页面上使用一次。即页面上不能重复出现相同的值的ms-controller。
<div ms-controller="test">{{@aaa}}</div> <div ms-controller="test">{{@aaa}}</div> <div ms-controller="test">{{@aaa}}</div>
由于test这个vm拥有一个叫$element的属性,它是保存其关联的元素节点,如果定义了多少个,那么它会保留最后的那个DIV。以后它的属性变化,只会作用最后的那个DIV。
vm的运作原理
avalon之所以使用Proxy, Object.defineProperty或VBScript来构造vm,那是因为它们创建出来的对象有一种自省机制,能让我们得知vm正在操作或访问了我们的对象。对于Object.defineProperty或VBScript,主要是靠将普通属性变成访问器属性。访问器属性内部是拥有两个方法,setter与getter。当用户读取对象的属性时,就将调用其getter方法,当用户为此属性赋值时,就会调用setter方法。因此,我们就不需要像angular那样,使用脏检测,就得知对象被修改了某些属性了。 并且能准确得知那些属性,及时地同步视图的相应区域,实现最小化刷新视图。
插值表达式里的内容与
ms-*的属性值都会转换求值函数, 比如说
` 变成function(){returnvmodel.aaa}
`ms-attr="{title: @name}"变成
function(){ return {title: __vmodel__.name} }此外,请不要出现
@aaa[@bbb].ddd,
@eee[ddd],
可能导致依赖收集失败, 无法更新对应区域
对于Proxy(智能代理),这最早发迹于firefox4,现在许多新浏览器都支持,它能监听外部用户对它的14种,比如说读写属性,调用方法,删除旧属性,添加新属性,被for in循环, 被in关键字进行存在性检测, 被new……因此之前所说的,不能监听没预先定义的属性, 这个难题被Proxy搞定了。
当我们得知vm的属性发生变化了,如何更新视图呢?在avalon2中,这个是由虚拟DOM来处理。
虚拟DOM比较复杂,大家看不懂可以略过。
虚拟DOM
avaon会在DOMReady对.ms-controller节点进行outerHTML操作 之前是直接用outerHTML,但后来发现outerHTML在各个浏览器下差异性太大了. IE6-7会对colgroup, dd, dt, li, options, p, td, tfoot, th, thead, tr元素自闭合 让我的htmlPaser跪掉 于是写了htmlfy,手动取每个元素nodeName, attrName, attrValue, nodeValue来构建outerHTML第2阶段,将这个字符串进行parser,转换为虚拟DOM 这个阶段对input/textarea元素补上type属性,
ms-*自定义元素补上ms-widget属性, 对table元素补上tbody, 在ms-for指令的元素两旁加上
<!--ms-for-->,
<!--ms-for-end-->占位符, 并将它们的之间的元素放到一个数组中(表明它们是循环区域)
并去掉所有只有空白的文本节点
第3个阶段,优化,对拥有
ms-*属性的虚拟DOM添加dynamic属性 表明它以后要保持其对应的真实节点,并对没有
ms-*属性的元素添加skipAttrs属性,表明以后不需要遍历其属性。 如果它的子孙没有
ms-*或插值表达式或ms-自定义元素,那么还加上skipContent,表明以后不要遍历其孩子.
这三个属性,dynamic用于节点对齐算法,skipAttrs与skipContent用于diff算法
第4个阶段, 应用节点对齐算法, 将真实DOM中无用的空白节点移除,并插入占位符, 并将需要刷新的元素保持在以应的拥有dynamic属性的虚拟DOM中
第5个阶段,放进render方法中,render方法里面再调parseView,parseView会调每个指令的parse方法 将虚拟DOM树转换为一个$render方法
第6个阶段,执行$render方法,生成新的虚拟DOM,与最早的那个虚拟DOM树diff,一边diff一边更新真实DOM.
以后VM的属性发生变动,就直接执行第6个阶段.
相关文章推荐
- 学 Win32 汇编[28] - 跳转指令: JMP、JECXZ、JA、JB、JG、JL、JE、JZ、JS、JC、JO、JP 等
- js和jquery如何获取(图片)真实的宽度和高度
- Jsrender初体验
- HTML/CSS: 用CSS与HTML实现表格的显示
- JavaScript的核心
- 《React-Native系列》31、 Fetch发送POST请求的坑与解决方案
- div+css布局小结
- NodeJs实现数字翻转
- JavaScript自执行函数
- Angular路由
- hammer.js---拖动滑块实现验证的小demo
- 设置text-overflow文本溢出隐藏时的对齐问题
- URI跳转方式地图导航的代码实践 h5打开地图
- js页面遮蔽层
- 常用的table表格排序 - sortablejs
- jquery填充select下拉列表,三级联动
- HTML 常用标签及示例
- nodejs实现求一个数的约数
- 新手学习AngularJS最佳项目:angular-phonecat官方案例
- JQ版图片的鼠标放上效果