这是前端框架比较和吐槽的第二篇。
Ember.js
Ember.js 的 extend 的写法很类似于 JQuery 或者是 Backbone.js,创建 Application,然后在它下面创建相应的 Model(Object)、Controller、Router、View 和 Template,这些都是非常类似的。但是它更为先进的地方在于,一些重复的样板代码,比如给 template 注入上下文并渲染,如果命名按照 CoC 的原则正确完成的话,都由框架自动完成,这就省去不少体力活。CoC 还体现在 URL mapping 上面,比如”/books/book_id” 配置在 books.index 的 Router 里,Controller 就是 BooksIndexController,Router 就是 BooksRouter,Template 就是 books/index。
除了 CoC 这个亮点,在解耦方面,Router 里面设置区分里 resource 和 route 的概念,既清晰,又简洁。属性绑定是另一个和 Backbone.js 比强化了的地方,依然遵照 CoC 的原则,如果属性以 Binding 结尾,绑定属性就自动创建,而计算属性则(方法的输出和某些属性之间的依赖关系)使用 property 方法来显式建立关联。事件方面使用 observes 方法并传入属性名来建立监听,其实和 Backbone.js 是差不多的。
我拿它不知不觉地和 Backbone.js 比较,最初还是因为 API 长得像的关系,后来才知道,其实这并不奇怪,因为核心开发人员 Tom Dale 说,灵感就是来自于 Cocoa、RoR 和 Backbone.js,有一些特性则怎么看怎么像语言特性,而不是框架特性,比如 Mixin。该文中他自己比较了 AngularJS 和 Ember.js。当然,这篇文章的题目是《Is Angular.js or Ember.js the better choice for JavaScript frameworks?》,自然少不了对 AngularJS 的吐槽(注:下面括号里的内容都是我的补充,并非来自 Tom):
- 比如讽刺 AngularJS 是一个 “by Google” 的项目,而不是一个真正的 “社区项目”;
- 比如 AngularJS 的 HTML 属性绑定的形式过于 “noisy”,而且难读,而 Handlebars 的表达方式更好(比如模板里面使用 {{#each}},而不是搞一个 ngRepeat 的 HTML 属性);
- 比如基于字符串的模板(对比 Angular 基于整棵 DOM 树上绑定属性实现)有诸多优势:预编译,不需要遍历整棵 DOM 树;
- 比如在服务器上渲染应用的话,Ember.js 不需要启动整个浏览器环境;
- 比如使用模板方式对惰性加载的支持;
- 比如 AngularJS 众所周知的 dirty checking 的性能弊端(要知道,和 AngularJS 的双向绑定相比,Ember.js 不仅支持双向绑定,还支持 “Data Down, Actions Up”的单向绑定)。
最后还说,1.0 版本已经 lock down 了,以后不会有大的变化的(这明显是在含沙射影那个谁啊~,不过 1.0 之前它自己也不怎么样)。
他虽然那么显摆 Handlebars 的好处,但是它其实也有一些明显的缺陷,最大的就是表现力上,我一开始被 AngularJS 震撼的地方一个是双向绑定,一个就是扩展了的 DOM,清晰而且解耦,相较而言 Ember.js 的这部分代码就容易显得啰嗦(代码表现力上,总体来说,用 AngularJS 的时候我能感觉到自己是时而做设计,时而写逻辑,但是用 Ember.js 的时候总觉得要么在捣鼓表达式,要么在折腾 API);再有就是 DOM 对象过度创建的问题,有许多标签的实现都是靠最后创建辅助 DOM 对象来实现的,最后就形成一大堆对页面加载和问题定位不怎么有帮助的臃肿的累赘们。解决方案是使用别的模板引擎来替代 Handlebars(比如 HTMLBars)。
Ember.js 的社区发展旺盛,生态系统也非常完备。事实上,Ember 要解决的问题(或者说野心)是一个大问题,是而不只是一个简简单单的框架问题。比如说其中的 Ember Data 是不得不提的,它做给数据模型层做了非常好的封装,和 Node.js 一起使用,通信 API 的部分,传输数据序列化的部分,都不用关心(实现遵照 JSON API),把注意力放在它往上的逻辑上就好了。再有一个是 Ember CLI,从这个就可以看出,它想要解决的是工程的问题,比如创建代码样板和配置各种依赖插件,甚至全栈的问题,而不仅仅是前端技术的问题。最后,想想 Ember.js 自己的口号——“creating ambitious web applications”,“ambitious ” 这个词是在确切不过了。
不过总的来说,Ember.js 还是一款比较复杂的框架,即便因为 CoC 的关系,配置和使用的代码量不大,学习门槛依然无法避免地比较高,甚至比 AngularJS 更高。另外,在选型的时候评估一个技术需要把明确依赖的插件扩展等等一股脑统统考虑进去,这些加起来就很大了。
最后,推荐一下这个 Quora 帖子,已经变成了 AngularJS 和 Ember.js 两派有理有据地 “有脑” 撕逼现场了,非常值得一读。
React Redux
这是我比较感兴趣的部分,让我多费一点口舌。
React 带来了诸多编程范型的融合,从 JSX 往大了说,本身声明式语言和命令式语言本身就像是天生的冤家,很少能被放到一起的,但是在 React 中我们看到了;在往细了说,像函数式编程等等风格都可以见到,尤其是它可以和携带语言层面特性的许多 lib 很好地融合,比如可以使用 Promise,可以藉由它们的力量把一大堆回调拉平,写代码的自由度很高。JSX(JSX 其实并非必选,完全可以用传统操作 DOM 风格的代码来操纵 DOM,但那实在是舍近求远了)让以往需要单独考虑的模板层面的代码组织,比如重用、解耦、引入等等,现在只需要在传统的 JavaScript 层面考虑了,和别的代码没有什么区别;再一个,以往间接的往模板传参和需要独立上下文(栈)来实现的状态保存,都变得直接而且简单。
说到状态,React 引入的状态机机制,即通过事件监听来更新状态(setState),从而自动调用 render 来渲染组件的方式,也实现了绑定。
我认为 React 本身的难度曲线是比较低的,尤其是和 Ember.js 等等这些 “充满野心” 的大块头比起来,自己定位清楚,它本身更多地贡献在 View 这一层的丰富表达上,单纯得很。但是,也要看到技术栈不同层面的扩展也非常火热。看起来低调,React 在干的事情是要革命,革了传统前端开发的命,比如 JSX 是要干掉 HTML 的,React Native是要取代诸多终端适配的解决方案,Reactor-Router 是要替代各种 URL Mapping 的……最后,终于发现他低调潜藏着的野心,他要侵入整个技术栈,它要形成一整个 “体系”。
紧接着必须要提 Redux,因为上面说了,React 更多的贡献是在 View 上面,本身并非一个完整的框架,于是 Flux 跳出来说:“这样吧,我来定义一些 pattern 以解决这个问题,至于你们爱谁实现谁实现去”,这才有的 Redux。有了 Redux,它才可以被归纳到框架的范畴里讨论,这也是 Redux 被放在这个段落标题上的原因。
于是为了谈 Redux,先谈 Flux,我觉得它最大的亮点在于解决了多个 View Model 的状态之间的同步问题。传统的 MVC 架构中(左图),分层清晰,但是存在的一个缺陷是,如果有多个 view,它们都要和同一个 model 交互,之间的关系错综复杂,于是一致性关系就很难处理,每添加一个 view,就要给每一个交互的关系去增加一个逻辑去解决。一旦这个 view 的数据变更,要引发相关联的 model 和 view 改变的问题,这完全不是 “对修改关闭” 了,不符合开闭原则(两图都来自这个 Facebook 的分享)。Flux(右图)则保证任何 view 的改变,都统一回归到上游,由 dispather 去分发给影响到 store,然后再 rerender 需要变化的全部 view,可以看到原来双向流动的数据箭头全部变成了单向,这样逻辑就清晰很多,也不需要那么多同步代码。多提一句,这种数据状态和 view 之间绑定的问题,其实无论是问题还是解决方案都由来已久,最经典的就是 CSS 在 DOM 上的绑定,于是数据状态变化的时候,只需要改变 CSS 的名字,view 就可以改变,甚至包括状态之间的转换效果都可以通过这个来完成;而另一个例子是之前提到的 AngularJS 等其它框架,它们则是用不同的双向绑定也解决了这个问题,各有优劣——下面会看到 Flux 这种方式的优势。
在这个分享中,另一个让人兴奋的地方在于,Virtual DOM Tree 的使用。对于 view 的更新难免会有大量的 rerender,但是是否一点点修改要把整个 component 全部渲染一遍?Flux 构建一个虚拟的 dom 树,在状态和数据变化完以后,比较新树和老树,找出差异的部分,然后在实际的 DOM 树上 “只更新差异”,从而减少了 render 的开销。
扯完 Flux 以后再来看 Redux,它很像是吸纳了以上想法的实现,并且改造和拓展了一下。当然有一些 Flux 的特性它没有采纳,比如 “dispatcher”,因为有了纯函数式的 reduce 方法来计算状态;再比如 Redux 是私自默认你只会使用不可变对象,而不会擅自改变其中的状态的。它自己号称自己是 “可预测的状态容器”(predictable state container),不过我们通常认为 React Redux 就可以成为一个好用的框架了。为什么说 “predictable”,是因为当状态变化和异步同时发生的时候(“mutation” 和 “asynchronicity”),整个系统中的状态和状态一致性是很混乱的,但是 Redux 就是要解决这个问题,把这些状态和状态的变化变成再编程过程中可以预测的:
- 只有一个数据源(store);
- 状态是只读的(数据流动的单向性:只能通过 action 去改变);
- 只能通过纯函数(reducer,而不产生任何外部影响)来获取新状态。
关于 React Redux 这套组合拳的打法,牛逼已经吹出去了,但是争议的地方也不少。比如对于通常的没有那么多从 model 到 view 交叉耦合的应用,这个解决方案有杀鸡用牛刀之嫌;在讨论中也有人担心 virtual DOM tree 对内存过度占用的风险,而且这种 immutable 的存储,以及新树和老树的比较,就像编程语言中对不可变对象的使用一样,在一些情况下会有性能的问题;函数式编程的思维对于很多人来说并不容易转变,因此代码往往远非最佳实践;如果要因为动画效果而维护状态的话,问题就更多,比如状态变化过于频繁,比如对于动画开始结束的回调方法会把状态耦合到 UI 去……
结束语
写到这里,对于 GWT、AngularJS、Backbone.js、Ember.js 和 React Redux 这几款常见的前端框架的比较和吐槽结束,它们都常用且具备代表性,我认为学习的价值是显著的。其实看看这些技术们自己吹牛逼(不要光看第三方撕逼嘛~)也是一件乐事,我来尝试戏虐地总结总结它们:
- GWT 说,人类的最大的问题,也是我要来解决的问题是,你们这帮 Java 狗的前端技术太屎;
- AngularJS 说不对,最大的问题不是人的问题,而是代码和绑定本身的问题,没有表现力,啰嗦无比;
- Backbone.js 说其实还是把有限的精力放到解决从 RESTful API 的调用到 view 的模型生成这一个流程上比较靠谱;
- Ember.js 说太幼稚,世界是你们的,世界是他们的,但世界早晚是 Ember.js 的。我有你们的一切,我还有一颗充满欲望的心;
- React 说,随你们怎么说,我知道一切状态,我可以预测未来。
至于其中的唠叨,不只是客观事实,个人口味也非常重,而且限于水平,当有许多不当和争议的地方,欢迎指正。至于其中的后两款我还有更深入的学习计划,会在以后记录更多的思考和讨论。其实还想涉及 Ext JS 和 Dojo Toolkit,不过有了上面这些以后,它们的典型性就没有那么强了,而且时间所限,就先作罢吧。
文章未经特殊标明皆为本人原创,未经许可不得用于任何商业用途,转载请保持完整性并注明来源链接 《四火的唠叨》