大半年前我就说过,MVVM是前端究极的解决方案,因此之后我大多数时间都在折腾avalon,成立了专门的QQ群与感兴趣的一起讨论。感谢第一批吃螃蟹的人,avalon发展得很快,GITHUB上的贡献人数达到8人,issues二百多个主题,各种组件也不断完善。时至今日,我可以自豪地说许多静待1.0的看客说,它终于来了!
下面是avalon的生态系统:
avalon的核心是avalon.js或avalon.mobile.js(移动端版本,针对手机与平板)。其中avalon.js兼容到IE6,满足中国的特殊国情。avalon.mobile.js是支持移动端,IE10起支持,它使用了大量高级API,性能远超avalon.js,并添加了触屏事件的支持。
然后是三柱臣:mmRouter, mmAnimate, mmRequest,单页应用最常用的三个组件。
接着是拖放,事件代理,组件键,验证框架,高级定位机制,组合键等乱七八糟的,用于锦上添花的
最后是UI库,比较简单的控件都做出来,重头的树,GRID,多级菜单正在规划中……
说一下我的初衷啊。我最早是做企业级应用,大量的表单,大量的列表,迭代频繁,虽然JQ已经出来了,但业务逻辑与DOM显示逻辑混淆在一起,维护起来非常不爽。于是我开发了ejs这个前端模板。前端模板在当前还是一个很新的概念,因此我当时发布在博客园,许多人不解。但比起在JS文件里进行锁碎的字符串拼接,它就是易读易维护。QQ空间,淘宝那时也已经使用前端模板了。那是09年的事。之后,前端模板就像雨后春笋地不断涌现,性能越来越高。
转辗到盛大创新院后,做圈乐这个视频项目的瀑布流,与大牛们共事,用的是qwrap,接触了许多新概念。我的框架由dom Framework更名mass Framework,大量精力耗在选择器引擎与模块加载器的开发上。那时requirejs也是刚刚起步, Sizzle也很粗糙,因此这两个领域都有大量同类型产品。我大量读库,实力蒸蒸日上。创新院大裁员,我避风到SDO做盛大通行证,我深切地体会到现行开发模式的掣肘之处。我在创新院还可以用前端模板搞定渲染问题,但这是一对一的。盛大通行证一个逻辑面向不同的视图,要求务业逻辑与DOM渲染逻辑彻底分离,要不没法进行下去。当时我只有求助于JAVA的设计模式,各种解耦。像后端spring,早已想到这一点,因此才有IoC容器这样的东西出来。在处理渲染方面,它们有庞大的标签库,这是面向组件开发的拓路者。为了更好的控制标签的行为,微软从MVC的基础上发展起MVVM。MVVM用尽了23种设计模式,让我们轻松地操作一个叫VM的特殊数据源就能自动同步视图,这种机制被称之为绑定。绑定在微软的体系中分为三种one way, one time, two way。微软出身的Steve Sanderson搞出了第一个前端MVVM框架knockout。当时,09年,别说MVVM,MVC在前端也是新事物。前端MVC的风头从jQueryMVC转到backbone。我的注意力也停留在backbone身上。backbone有着后端JAVA MVC框架的优缺点,分类极细,责任明晰,但这也意味着繁锁,定义了许多类,但好像又什么也没干。其中这当中的类可以由框架自动生成。
模块加载器开发了十多个版本号, 我着手开发UI库了。这时愈感JQ的不便。UI是一种界面,很早以前人们就发现,干这活是声明式语言的强项。我们平时熟悉的javascript, C, C++, java则擅长做逻辑。于是才有前端模板这东西,将数据源往模板一套,界面就出来的。但之前的模板都属于字符串模板,无法对生成的元素节点进行后继操作。不过,据元素节点的innerHTML的能耐来看,它也不执行里面的script脚本。jquery的html可以打破这桎梏,其实也基于eval处理的。最终,knockout还是走入我的视野。很大一个原因是我一直活跃于博客园,博客园属于微软系,微软这个东西虽然难用一点,但在当时也算是至宝!
knockout是一个伟大的框架,一直很低调。作者是后端的人,很JS用得炉火纯青,大量的闭包,加之方法名,变量名是找C#那一套,名字巨长,搞得很难读懂它的源码。knockout的思想很简单,把用户的操作分成两部分,赋值与取值。赋值时,它就想办法同步视图。取值时,它就试图取得各种操作的依赖关系。有了依赖关系,就可以构成观察者模式,让视图同步成为可能。为了达到这目的,必须让框架知道当前进行的是什么操作,操作的作用方是谁。由于当时javascript访问器属性还鲜为人知,在旧式IE也有兼容难题。knockout取采的策略是将所有属性都转换为函数,根据函数有没有传参就可以辨识用户的行为了。对于数组,它是重写所有原生方法。avalon0.1-0.3就是追随knockout的步伐,逐渐了解它的其他概念与实现原理。比如说计算属性,取值属性,视图刷新函数,各种常用的绑定。
我之前谈如何写框架,就提到三个原则:复杂即错误,数据结构优于算法,出奇制胜。knockout的源码太复杂,导致想参与的人参与不进来。为了降低上手难度,你无论是从接口到实现,一定要条理清晰,不能到处是黑魔法与飞线方案。虽然avalon的源码只有一个文件,里面的条理是非常清晰的,并且刻意使用一些特殊的注释来隔开各个功能区。API尽可能少,许多重要的接口都不暴露出来,以后就可以随意折腾,不影响用户使用了。在厘清knockout的实现机理后,我就是由单纯的做轮子,转为发明新轮子。首先,属性转换为函数这一用法必须去掉,太扭曲了。ember的做法是使用两个上帝set, get方法来接管一切取值赋值操作,比knouckout好多了,但依然不直观。angular是直接对控制器等函数取toString进行编译,用法简单了,但实现难度非常高,加之它其余设计把这易用性泯灭了。于是我转来转去,还是求助于访问器属性解决了。这过程不是三言两语说得清的,但从结果来看,就是Object.defineProperties与VBScript类的Set, Get, Let语法。用户传入一个对象,然后我转出另一个跟它非常相似,好像只是添加了几个属性的对象,然后用户对它的属性进行操作,它就能同步视图,相当fantasy、 magic!易用性爆表了!
其次是绑定属性的设计, knockout是使用data-bind对应一个没有两端花括号的JSON对象字符串,看似规整划一,但这意味着内部要实现一个parser来转换。后起之秀的angular则友好多了,但最终我是参照了rivetsjs。缘由它的绑定风格能提供更多信息,更易分解,用户也很易理解。这样我就不用耗时实现一个庞大易出错的parser了。编码不同于建筑,BUG与规模是成比例增长,控制代码量是avalon在编写过程的一个重要指标,至少它还没有超过4000行。
最后根据用户的反馈,不断增加新功能,在增加新功能时不断引入BUG,然后修BUG,改好了,发现性能下降,于是提高性能。性能提高了,用户又提出新功能,然后……周而复始,不断与需求与BUG作斗争。这就像一个普通的产品的开发流程。不同的是,这个产品完全是属性于你,你有杀生大权,你能完全享受用户对你的赞誉。但反过来,其实我还是得感谢那些敢吃螃蟹的人,那些提BUG的人,那些pull request的人。正因为,这项目比mass Framework发展得顺利多了。它与最初的第一版几乎没有一行相同的代码,尽管我尽量避免API的改动,API最后也有改动了,变得更加精巧。
0.4-0.7是脱胎换骨时期,它一直留在mass Framework项目的内部。0.72独立出来,可以独挡一面了。这时不断改进绑定的实现,一直到0.94才稳定下来。难点在于ms-each, ms-repeat, ms-with, ms-widget(ms-ui), ms-if这五种API。它们的特点都是有节点插入操作。有的是插入了移除,移除了插入, 有的是循环生成一堆相同节点,有的是按照一个模板生成一个控件。由于绑定都有生命周期的概念,它的实现方式直接与它是否在DOM树挂钩,但如何是框架自己临时移出DOM树呢,于是就引发一系列问题了。幸好最终都解决了。这基本是靠时间磨出来的,今天想不出来,睡一觉明天就想通一点点,后天又解决一点点……
facebook实现一个react的MVVM框架,炒作一个叫反应式编程的概念。其实它的核心就是knockout的弱化版。对我看来,用数学的自变量,因变量来表达它们之间的关系更适合。其中绑定属性,监控数组的方法就是自变量,这是由用户直接操作的。计算属性,求值函数,视图刷新函数就是因变量,由下层的绑定属性,数组方法所驱动。0.99起,开始大量性能优化,把求值函数,视图刷新函数都共享了。但这些东西,用户根本不需要知道,说不定某天发现更高效的实现方式,它们就不存在了。所有要注意之处,在《入门教程》已经标明。要深入可以看《最佳实践》。再者看源码,这些概念只是辅助你看当前版本的源码。想成为高手,还是从源码着手。只是想快速完成任务,看我的《入门教程》就够了。
感谢这个时代,正因为有了IE6才有我们前端,正因为有前端才有我们这些民科外行沾光IT的福利。
迷你MVVM框架在github的仓库https://github.com/RubyLouvre/avalon
官网地址http://rubylouvre.github.io/mvvm/
有关avalon的最佳实践或注意点请看这里, 这个我每次发布新版本都可能在这里加东西
1.0的相关改进
- 优化executeBindings, 插值表达式形式的文本绑定不需要removeAttribute
- 优化scanText, 它生成的绑定属性不需要element元素
- fix duplex 绑定, 从左到右检测匹配的vmodel
- duplex 绑定添加表单检证的钩子
- 优化监控数组的sort, reverse方法
- avalon.fn.css支持设置多个属性,优化旧式IE下的取宽高
- 重构ms-widget, 过滤中间生成的VM, 支持在配置对象里指定ID
- 重构fixEvent
- fix ms-class BUG
- 优化nextTick
- 为性能考量,弱化avalon.Array.ensure
- 移除data.remove属性,通过是否存在求值函数evaluator来移除绑定属性
- 共享所有视图刷新函数
- clearChild 更名为 clearHTML
- IE67下添加getAttributes方法,直接使用正则抽取绑定属性
朋友们用avalon做的东西
- 移动应用:读酷
- chrome插件:饭否客户端
- 为知笔记
- 金山WPS office 会员中心
- 企业级应用:超博CRM客户关系管理系统(帐号:crm_ceo 密码:nncb_ceo)
- uliweb Python框架与avalon的组合示例
- avalon+jQuery实现域名注册查询
- 路由器示例
- 边锋活动页
- 记者考试题
未来avalon的发展方向就是三大重型UI组件:树,GRID,多级菜单。望大家也多多贡献,一起完善这个生态圈。