你很喜欢Gmail和Trello之类的单页面应用,但是不太确定该从何开始。也许你的JavaScript代码是如此的杂乱无章,以致于你很想在下一个项目上尝试下JavaScript MVC库和框架,却苦于没有头绪?我正在撰写一本单页面应用的书,所以我阅读了大量网上的相关资料。在这里我尝试提供一些看法,希望可以帮助你下决定。
简介
这里讨论的是时下最热的框架,AngularJS、Backbone、Ember和Knockout。同时提到了Batman、CANjs、Meteor和Spine,但是没有详细展开。
我们从多个不同的角度考察每个项目,包括社区、领导、成熟度、大小、依赖、互操作性、启发、理念和特性。
社区
社区是一个衡量任何开源项目健康程度的重要指数。以下表格显示了GitHub上每个项目的关注者数量。
你当然不该仅仅根据这些数据做决定,但是它们确实为你提供了关于这些框架的一些感性认识:
最主流:
- Backbone.js
- AngularJS
正在高速成长的:
- AngularJS
- Meteor
- Ember
- Knockout
关注总量较低但是增长迅猛的:
- CANjs
增长性
特别值得注意的是AngularJS 13个月以来的惊人增长(379%)。在你做决定的时候要考虑上这一点。下面的图表比较了13个月以来GitHub关注者的增长速度,可以看出某个项目的社区成长的速度。考虑到原先的社区大小,Meteor(130%)、Ember(104%)、Knockout(76%)和Backbone(64%)的增长速度也很惊人。
领导
了解项目的核心开发者的背景,他们创建框架时尝试解决的问题,有助于你欣赏他们在设计上的决策和动机。例如,David Heinemeier Hansson,流行的Ruby on Rails框架的缔造者,是37signals的签约开发者,从事项目设计,每周只有10小时能花在开发框架上。Ruby on Rails事实上是从他和37signals的签约工作中提取出来的。这一背景有助于你理解为什么这个框架需要将开发效率提升到极限——这意味这使用大量的约定(已经做出的决定)和支架程序(生成的代码)。下面,我将介绍JavaScript MVC 框架的缔造者,也许也能激起你对他们的工作的欣赏。
Backbone
Jeremy Ashkenas和DocumentCloud
Jeremy Ashkenas是CoffeeScript编程语言、Backbone.js JavaScript框架和Underscore.js JavaScript工具库的创立者。根据*,他现在在NYTimes/DocumentCloud从事互动新闻方面的开发。
图片来自The Canadian University Software Engineering Conference。
AngularJS
AngularJS最初由Google的Miško Hevery和Adam Abrons于2009年开发,当时是一个在线JSON存储服务的一部分。Abrons后来离开了这个项目,但是在Google工作的Hevery继续开发,和Google的员工Igor Minár、Vojta Jína一起维护这个库。
图片来自Devoxx 2012
Knockout
Steve Sanderson是Knockout的原作者。Steve Sanderson现在为微软工作,他所在的团队开发ASP.NET、IIS和其他web项目。他以前以外包开发者或顾问身份为Bristol周边的客户开发.NET下的软件,他还为Apress写了一些书,包括《Pro ASP.NET MVC框架》。
Ember
Ember核心成员中最知名的公众人物是Yehuda Katz和Tom Dale。
Yehuda Katz是Ember.js、Ruby on Rails和jQuery的核心开发者,他白天的时间花在他创办的创业公司,Tilde Inc.。Yehuda是畅销书《jQuery in Action》和《Rails 3 in Action》的作者之一。
Tom Dale原先在SproutCore团队工作。他以前是苹果软件工程师,在开发MobileMe和iCloud应用时精通了前端JavaScript技能。
图片来自Ember Team
Meteor
Meteor的开发团队刚刚得到了1.12千万美金,所以他们可以全职开发。他们的团队有12名开发者,每个开发者的简历都让人印象深刻。这个团队拥有雄心勃勃的计划,超越了大多数专注于组织客户端代码和状态的JavaScript MVC框架。Meteor是一个全端框架,包括服务器架构和数据库。
成熟度
评估框架的成熟度可以帮助你理解在项目中使用新技术的风险。未经考验的新框架在文档、扩展性、稳定性(API改变)和支持(寻找了解该框架的开发者来维护代码)方面可能存在问题,这些问题可能导致出乎意料的结果,即使从其他方面看起来决策很明智。需要考虑的项目包括:多少生产环境下的应用使用这些框架?这些应用有多少用户?文档是否良好?例子和教程是否充足?例子是否过时?API是否稳定?其他开发者了解或正打算了解这门技术么?
- Backbone(最成熟)
- 大量3年以上的生产环境应用,包括GroupOn、 FourSquare、USAToday、DocumentCloud。
- 良好的文档。
- 良好的示例代码,但是很多都过时了。
- API非常稳定。
- GitHub上有大量关注者。
- AngularJS(成熟)
- Google将其用于生产环境,但是不像其他项目一样拥有长久的历史记录。
- 良好且持续改进的文档。
- 大量的示例代码。
- GitHub上有大量关注者。
- Knockout(成熟)
- 用于生产环境至今已有2年。
- 良好的文档,内含类似jsfiddle的代码样例。
- API很稳定。
- 大量的示例代码。
- GitHub上有大量关注者。
- Ember.js
- 在两年的开发之后,第一个可用于生产的发布(1.0)在2013年8月30日放出。
- 文档正在改善。
- API在1.0之前都不稳定(这是有意的)。
- 大量示例代码,由于1.0前的API变动,有些已经过时。
- GitHub上有大量关注者。
- Meteor
- 处于开发的早期阶段,较多地用于示例应用
- 文档不错,然而尚不完整
- API仍处于变动之中。
- 一些示例代码。
- GitHub上有大量关注者。
大小
了解每个框架的大小,它会在你的应用中增加多少分量是很重要的。大小会影响性能,不过它同时也会暗示你这个框架的雄心有多大,你学习它可能需要花多少时间,以及它提供多少帮助你构建应用的方式(即特性和鲁棒性)。一个框架的野心越大,特性越多,通常也就意味更难在应用的页面上将它与其他部件组合。轻量的框架更像一个库,将他集成到你的项目中所耗费的精力也相对较小。
包括Backbone和Spine在内的一些项目为自己的轻量而自豪,这些项目更多地将自己视作库,而不是框架。通常这些小型的框架留下了空间,你可以使用你自己的库来实现特定功能,例如模板和路由。我在讨论这些框架的特性的时候将继续讨论这个话题。
包括Ember和AngularJS在内的其他项目雄心勃勃,更适合叫做框架。它们通常拥有更多内建的特性,更少依赖外部库。
下面的列表显示了我更倾向于将哪些项目归入库或框架:
库 | 框架 |
---|---|
Backbone | Ember |
Knockout | AngularJS |
Spine | Batman |
CanJS | Meteor |
依赖性
使用这些项目构建真实世界的应用的时候还需要哪些库?以下的图表展示了为了保证开发效率每个库所需的依赖,以及这些依赖的大小。
通过从cdnjs下载库,我们收集了这些数据。在实践中,大多数的项目会使用jQuery配合这些框架处理DOM,因为需要动画和AJAX。在移动应用中,使用Zepto.js来代替jQuery处理DOM不是什么稀奇的事。Zepto.js是一个比jQuery轻量得多的库。虽然Zepto.js不支持Internet Explorer,但是移动应用通常不需要为此操心。AngularJS包含了一个jQuery 的缩减版jQLite。但是如果你在项目中使用了jQuery的话,它会被覆盖。AngularJS团队鼓励开发者,如非必要,不要添加完整的jQuery库。为了帮助你做出正确的选择,下面的表格同时显示了移动版(假定使用Zepto.js)和web版(假定使用jQuery)。
互操作性
这一部分讨论是否框架设计为控制整个页面或者它可以被用于现存页面的一个部分——你可能想渐渐将新技术引入现有的项目。前面的库和框架的讨论基本可以体现每个项目的互操作性,库更倾向于很容易地集成到现存的项目,而框架为你做更多的事,但是不容易和其他项目配合。
AngularJS
AngularJS可以和其他库很好地配合,但是它鼓励开发者们考虑是否可以不用jQuery和jQueryUI.事实上Angular内置了一个jQuery的子集jqLite。遵循这一实践的理由是让单元测试更容易,因为很多依赖库和插件设计的时候没有考虑单元测试,相应地更难和单元测试配合。在实践中,大多数的开发者最终还是因为某些特性使用了jQuery.
Backbone
由于Backbone的小尺寸和无预设的架构,将其包含在众多流行的客户端库和服务器端技术中很容易。
Ember.js
被设计为在运行时控制整个页面,所以不太适合用于页面的部分。
Knockout.js
可以在项目中作为小组件使用,不控制整个页面。
启发
记者采访音乐家时最爱问的问题是“你在成长的时候听哪些艺术家的音乐,或者说,谁启发了你?”这个问题常常使得读者能够预期音乐家的声乐。这些框架中大部分的观念都不是全新的,而是来自于创造者以前工作的项目中喜欢的部分。这一部分总结了我从框架创造者的访谈中收集到的关于启发的信息。
AngularJS
HTML类的声明性的语言,Adobe和Flex、微软的WPF\Silverligt等RIA技术给AngularJS的影响很深。这些声明性技术没有”主体“方法,仅仅表达需要发生什么,而不指定具体实现。视图和模型中的数据双向绑定是这一声明式编程风格在绝佳例子。此外,在Google的服务器端Java代码中大量使用的依赖注入和IOC容器(特别是Juice)也启发了AngularJS的创造者。他们重视单元测试,需要框架被设计允许依赖注入,这样测试就可以从其他应用层剥离出来,运行起来也会更快。
Ember
Tom Dale在Quora上谈了Ember受到的影响:
Ember.js刚开始开发的时候,我们从Cocoa等本地应用程序框架引入一些概念,不过后来我们觉得这些概念弊大于利,或者说它们和Web应用程序格格不入。因此,我们开始从Ruby on Rails和Backbone.js 等开源项目中寻找灵感。因此,Ember.js结合了本地应用的强大和现代web的轻量。
此外,Ember.js是SproutCore JavaScript库的进化版,SproutCore停止仿效Cocoa而更多地借鉴jQuery的时候,Ember诞生了,理解这一点很重要。
Knockout
hanselminutes的播客提供了Steve Sanderson受到哪些方面启发的背景信息。总结一下,MVVM设计模式和微软的WPF、Sliverlight等声明性技术是最大的启发者。你可能会发现Knockout的最佳特性——声明性的数据双向绑定——和Anjular相似,因为两者的启发者是相似的。
理念
报纸在报道新闻的时候努力保持中立。唯一的例外是编者案,鼓励表达观点,作者通常在问题上占据一个强烈的立场。但是大多数情况,这两者既不是严格的中立报道,也不是强烈的意见表达,而是位于两者之间的连续统。技术框架也有类似的划分,即是否强主张。例如,Ruby on Rails推崇约定优于配置,并且为开发者做了大量决定,包括文件结构和数据访问。相应地,它被认为是强主张的。其他Sinatra类的服务器端框架更轻量,并不预设文件结构和数据访问。相应的,被看成是无主张的。服务端框架有理念,客户端JavaScript MVC框架同样有,我们讨论的框架也可以被置于强主张和无主张的连续统中考察。让我们看看每个项目,然后讨论他们的理念。
Backbone: 无主张
Backbone是最开明的框架,极度无主张,允许开发者做出自己的决定,有时这会导致代码差异过大而难以维护。唯一的例外是Backbone假定服务器端有一个REST服务,我会在特性部分详细讨论这一点。这一假定可以通过覆盖模型的sync方法来绕过。
AngularJS: 强主张
AngularJS有较强的主张,特别是它强调可测试性和依赖注入。此外,HTML类的声明性编程很棒的理念也在框架中广泛体现。
Ember: 极度强主张
Ember力求开发者仅对应用的特有部分作决定,其余全部交给约定和支架。这个理念和Ruby on Rails和jQuery很接近。这一理念最好的表达出现在emberjs.com的网站上:
不要浪费时间做那些无关紧要的选择。Ember.js吸纳了常见的惯用法,因此你可以专注于应用的特殊部分,而不是重新发明*。
Ember标准化了文件和url结构,当然,有必要的时候也允许你覆盖这些设定。你可以预期的是大量的代码会为你生成,大量的类似文件结构的约定。相应地,你需要做出的常规选择更少,因为框架已经为你选定了合理的默认值,你可以着手构建你的应用的特殊部分。
Knockout: 无主张
路由和数据存储留给开发者决定。不预设文件或URL结构。甚至允许用基于字符串的模板替换声明性的基于DOM的模板。
特性
我们可以将这些JavaScript MVC框架看成是帮助开发者构建单页应用的常用特性集合。每个框架实现这些特性的方式,或者不实现这些特性的方式(通过其他库来补全框架的功能)是值得留心的。
JavaScript MVC框架的主要特性是什么?
- HTML和客户端JavaScript对象模型的双向绑定
- 视图模板
- 数据存储(本地存储或者通过服务器存储到数据库)
- URL路由(确保后退按钮和搜索引擎正常工作)
除此以外,一些框架提供常用的语言层面的服务,例如通用的pub/sub事件模型和面向对象的继承支持。
数据绑定
这是最受吹捧的特性。你通过HTML input修改了数据,绑定到input的JavaScript对象马上更新,其他绑定的用户接口元素也随之更新。在很多框架中,反之亦然。如果你修改了JavaScript对象,html会自动刷新。这是一个web上的双向的数据绑定,我们在Flex、Windows Forms、WPF等富客户端应用框架中见过这类绑定。以下的表格显示了哪些框架支持数据绑定。
有些人可能持有异议,因为Backbone和Spine部分支持数据绑定。但是我觉得大量工作需要留给开发者,保险起见,不如说这些库不支持这些特性。
视图模板
客户端的JavaScript数据模型需要穿插在HTML中,这些框架采用两种方式解决问题。
基于字符串的模板(目前最流行的是handlebars.js),将字符串、文本模板中的动态部分替换为模型中的数据。字符串常被提到也饱受争议的特性之一是性能。其缺点是调试控制语句类的逻辑很困难。
基于DOM的模板拥抱标记语言的声明式本性,这是开了挂的html,通过html中的附加属性来描述需要的绑定和事件。这些库需要的代码大大减少,为开发者做了很多事情。
模型(可观察的:跟踪改动)
一些框架(Backbone、Spine)更专注于模型,要求开发者在base模型的基础上扩展JavaScript类,通过.get()
和.set()
访问属性,这样就能跟踪改动,模型的变动也会触发事件。KnockoutJS让开发者在原始的JavaScript对象上应用可察封装,然后通过object.propertyName()
访问属性(小心,别丢了括号)。
其他库(AngularJS)对页面上的所有绑定的DOM元素作脏检查,因为没有标准的get和set访问器。因此将这些库用于大页面时将导致性能问题。这些库不仅需要更少的刷新模板的代码,也不需要你使用特定的get和set访问器来修改模型中的数据,所以你可以使用原始的JavaScript对象。这大大提升了开发效率,这一点在框架的初学者身上体现得尤为明显。
数据存储
这些框架通过以下方式将数据存储到服务器
- 与REST服务自动同步
- 需要开发者自行实现ajax调用,调用针对返回json的web服务
- 同时允许以上两种方式
REST
一些框架默认预设后端有非常整洁的REST JSON服务,并且,至少在默认的情况下,前端与后端频繁交互,在后台异步更新数据,而用户界面响应流畅。这些框架内部使用jQuery或Zepto发送合适的AJAX请求给服务器。用户界面的HTML DOM元素监听应用的JavaScript对象模型的改动,同步机制得到模型属性的更改提醒,将改动发送给REST服务,确保模型和服务器同步。
在线和离线
Backbone默认在客户端保存数据之前发送请求,这样服务器端和客户端同步就很容易。和Backbone非常类似的Spine框架,采用了不同的方式,在异步发送请求到服务器前,先在客户端存储记录,这提供了更好的用户界面响应,在移动应用中常发生的离线状态下也能工作。如果你的项目需要支持离线,需要了解清楚框架对该特性的支持
DIY
这些框架要求开发者使用$.ajax
(jQuery)来调用服务,或者添加兼容的开源库处理数据存储需求。
数据存储特性
Meteor之类精心制作的框架拥有更完整的数据存储方案,但是要求服务器端有MongoDB数据库。这类方案试图提供一个扩展性优异的默认解决方案,提供从头到尾的JavaScript开发体验。
以下的表格总结了每个框架是如何处理数据存储的。
路由
将URL路由映射到JavaScript函数,可以支持浏览器的后退按钮。单页应用最大的缺点之一是由于页面无刷新,浏览器历史不会添加条目,所以后退按钮通常无法将用户带回页面的前一状态,除非开发者在主要的状态改变时做一些额外的工作,通过在URL后附加井号,或者,使用现代浏览器的push和pop状态,实现状态跟踪机制。总之,大多数项目都提供基本、初步而有用的功能。Knockout的做法很简单,允许你使用其他的第三方开源库。
苹果对苹果
考察各个框架的特性之后,我发现我并不是在做一个“苹果对苹果”的比较。一个更为公平的比较也许是将AngularJS和EmberJS之类全面的框架与配合第三方库使用的Backbone和KnockoutJS之类的MV*框架进行比较。具体来说,下面的比较会更有意义:
- AngularJS
- EmberJS
- Backbone 搭配 Marionette
- KnockoutJS 搭配 DurandalJS、HistoryJS或SammayJS。
以后的博客中我会继续深入。
告诉我更多
为项目选择JavaScript MVC框架时有大量需要考虑的事项,我希望本文是一个良好的开始。请在评论中分享你使用这些框架的体验,包括它们的优异之处,也包括它们使用上的陷阱。
原文 Choosing a JavaScript MVC Framework
翻译 SegmentFault