Angular.js 拥抱 HTML/CSS
Misko Hevery(Angular.js的开发者之一)回答了这一问题,他的主要观点如下: 在HTML中加入太多逻辑不是好做法。Angular.js只放置绑定,而不是逻辑,建议把逻辑放入控制器中。但绑定同样是信息,通常,这些信息可以放在三个地方:
- 代码。但这使得程序模块化很成问题,因为HTML与代码紧密耦合,要想重新组成一个应用程序非常困难。
- HTML。这正是Angular.js所做的。除了放置绑定信息外,你不应该在HTML中做任何事情。任何逻辑都不应该放在这里,否则会导致各种问题。我认为Angular.js做的绑定相当好。
- 元数据文件:不知道是否有人这样做,如果这么做,就产生了一个新问题,你需要在代码中把HTML和元数据结合起来。
Angular.js的独特之处在于它拥抱HTML/CSS,其他一些框架提供了它们自己的API,偏离了HTML。Angular.js在所有框架中是能体现声明式编程范式的。声明式编程非常适合用来编写用户界面,编写逻辑则交给JavaScript。 Angular.js允许你扩展HTML,所以你在使用Angular.js过程中遇到的任何问题都可以很容易地克服。
Ember.js 更社区化、更适合生产环境
Tom Dale(Ember.js的开发者之一)仔细比较了Angular.js和Ember.js.
Ember.js 由来
Dale首先来介绍了Ember.js项目的由来。从2009年开始,我就一直在苹果公司参与 SproutCore 项目的开发,SproutCore 是一个 类似Cocoa的JavaScript开源框架,后来演变成了
iCloud。当时,我身边是一些世界上最好的Cocoa开发者。 问题在于,客户端应用程序这么多年来似乎并没有真正新的突破。自80年代以来就一直遵循的基本模型——代码运行在本地计算机上,从网络上获取数据,然后在本地处理,并显示在屏幕上;而如今唯一改变的是——代码运行在浏览器的沙箱环境中,然后加载所需的“二进制”文件,而不是由用户安装到硬盘上的文件。 在考虑这些问题时,我首先去想,在我们之前,人们已经做了什么?我认为框架的作用无需争辩。比如Cocoa,无论在Mac还是iOS上,Cocoa都可以让开发者轻松编写受用户喜爱的应用程序。 我们希望开发者能够创建雄心勃勃的、能够与本地应用竞争的Web应用。要达成这一目标,开发者需要先进的工具和正确的理念。 Ember.js刚开始开发的时候,我们从Cocoa等本地应用程序框架引入一些概念,不过后来我们觉得这些概念弊大于利,或者说它们和Web应用程序格格不入。因此,我们开始从Ruby on Rails和Backbone.js 等开源项目中寻找灵感。
Ember.js 更适合生产环境
在Dale看来,与Ember.js相比,Angular.js更像一个研究项目。比如,在学习文档中,Ember.js主要讨论模型、视图和控制器,而Angular.js指南要求你去学习一些类似于范围、指示符和transclusion方面的内容等。 一些大公司已经在Ember.js上投入了大量时间和精力,比如ZenDesk对Backbone.js失望后使用Ember.js重写,Square的整个Web层面也是基于Ember.js的,Groupon的移动版Web应用也是使用Ember.js开发的。此外,还有很多创业公司通过Ember.js获得了成功,并开始回馈Ember.js社区。 而目前所看到使用Angular.js开发的大多数应用程序只是演示项目,或是Google的内部项目。
Ember.js 更社区化
Yehuda(Ember.js开发者之一)和我也一直积极邀请真正的用户参与Ember.js框架的设计和维护,这可以确保我们在Ember.js中添加的功能对于实际开发是有用的。 事实上,在过去的几个月中,大多数Ember.js开发工作都是由Ember.js社区的核心贡献组完成的,他们来自不同的公司。如果Yehuda和我哪天有什么事情,或者我们的公司倒闭了,Ember.js还将会持续发展。这是一个真正的社区项目,而不是“Google”项目。
模板
Angular.js使用有语义意义的属性(比如data-ng-repeat)来实现模板。 而Ember.js使用Handlebars来描述HTML。 Handlebars语法(类似
{{#each}}
),和Angular.js那样使用额外的属性做法,哪种更美观,是一个见仁见智的问题。我个人认为,HTML属性有点杂乱,可读性要差些。当然,如果Ember.js不存在,而我又必须使用一个使用了数据属性的框架,那么我会考虑Angular.js。 抛开美观不谈,我相信,
Ember.js使用基于字符串的模板有如下优势:
- 模板可以在服务器上预编译。这意味着减少启动时间,也意味着渲染一个模板可以像调用一个函数一样简单。而Angular.js需要你在应用程序启动时遍历整个DOM,你的应用程序越大,启动速度越慢。
- 如果你想在服务器上渲染你的应用程序(方便搜索引擎索引或让首次加载时显示速度更快),Angular.js需要启动整个浏览器环境,例如PhantomJS,这是资源密集型的。而Handlebars是100%的JavaScript字符串,所有你需要的只是node.js或Rhino之类的东西。
- 如果你的应用程序变得越来越大,那么字符串模板可以很容易地分割和惰性加载。
此外,Handlebars只让你绑定属性,而Angular.js允许你嵌入实时更新的任意表达式。刚开始很多人认为这是Ember.js的局限性,但实际上:
- 使用JavaScript来创建可计算属性非常容易,它可以包含任意表达式。Ember.js只要求你指定你的依赖,这样在更新时可以智能些。
- 一旦有新的变化,Angular.js就必须重新计算这些表达式,这意味着需要在你的应用程序中绑定更多的元素,因此速度会变慢。
- 因为Ember.js只允许你绑定属性,我们将可以很容易地利用ECMAScript 6的性能优势,如Object.observes。由于Angular.js发明了自己的带有 自定义解析器的JavaScript子集,这对于浏览器来说,优化代码变得比较困难。
Angular.js通常依靠一种叫做“dirty checking”的机制来确定对象是否已进行更改。在你扫描每个对象和其所有绑定属性时,比较当前值和之前已知的值。如果它发生了变化,就需要更新绑定。但Angular.js开发者非常聪明,使用“脏检查”,你不需要使用accessors。你可以用
person.name = "Bill"
来代替
person.set('name', "Bill")
,就像在Ember.js 或 Backbone.js中的一样。然而,使用“脏检查”,你无法一次有超过2000个绑定对象。 我认为这很好地说明了Ember.js 和 Angular.js理念上的区别。Ember.js 和 Angular.js都力求简单和易用。而Ember.js使你不必担心代码中是否有超过2000个绑定。如果你正在编写大型应用程序,那么你已经解决了你所担心的最大的事情。对于中小规模的应用程序来说,Angular.js同样是伟大的,因为这些应用程序不会触及Angular.js的限制区。 在Ember.js中,我们总是希望利用浏览器和语言中的新功能,以便使事情变得更容易。例如,一旦ES6中
代理对象(proxies)可用,我们不会再要求你使用
get()
和
set()
。 所以这就是为什么我认为——如果你想构建雄心勃勃的应用程序,你应该选择Ember.js。 此外,在开发过程中,我们对于性能方面和如何利用语言新特性方面也考虑了很久。Yehuda Katz和我一起开发Ember.js,他同时也是TC39(负责JavaScript下一个版本的制定)的成员,在此方面相当有经验。
Angular.js符合Web的未来
angularjs_scaffold的开发者Patrick Aljord也参与了讨论。
angularjs_scaffold是基于Angular.js编写的针对scaffolding视图的Rails插件。 Patrick Aljord阐述了选择Angula.js的理由。 事实上,我原本打算在项目中使用Ember.js,因为我比较信赖Yehuda(Ember.js开发者之一),他在Rails和jQuery方面的工作很杰出。但是Ember.js中随时会变化的API和匮乏的文档,使我一再推迟使用它。偶然发现了Angular.js之后,我被它吸引了。 正如Tom Dale(Ember.js开发者之一)所说,Ember.js受到了Cocoa 和Rails启发。问题在于,在Ember.js下工作,我并没有真正感觉到像在写一个Web应用程序。而Angular.js让我感觉像在写一个Web应用程序,它真正支持所有的Web概念,并以一种非常自然的方式来扩展HTML。 事实上,Angular.js并没有使用自己的对象或重写JS方法,当你使用Angular.js时,你就使用了纯JS,并且Angular.js实现的许多概念都将直接进入下一个版本的Javascript中。 学习Angular.js,就意味着学习未来的Javascript,而学习Ember.js,你只是学习到了Ember的特有概念。 来看个例子。HTML是伟大的,因为它是声明式的,如果想要定义一个段落,你只需写如下代码:
<p>Hello world</p>
但是如果你想非常动态地实现?你需要通过类似于下面的代码来引导浏览器:
<p id="greeting1"></p>
<script>
var isIE = document.attachEvent;
var addListener = isIE
? function(e, t, fn) {
e.attachEvent('on' + t, fn);}
: function(e, t, fn) {
e.addEventListener(t, fn, false);};
addListener(document, 'load', function(){
var greeting = document.getElementById('greeting1');
if (isIE) {
greeting.innerText = 'Hello World!';
} else {
greeting.textContent = 'Hello World!';
}
});
</script>
来看看Angular.js如何实现:
<p>{{hello}}</p>
再来看一个示例,如果你要遍历一个数组,只需:
<ul>
<li ng-repeat="element in array">element</li>
</ul>
这个语法看起来像新的 MDV标准。这看起来比Ember.js更加简洁。另外,Angular.js被优化得非常快,开发团队通过如下措施来实现:
- 脏检查
- 只检查当前视图
- 只在变化发生时检查
- 通过和Chrome团队协作来利用JIT
一些显示Angular.js的速度要快于Ember.js,例如
Angular VS Knockout VS Ember。 Angular.js未来会拥有可复用的组件,这允许你编写非常简洁的代码。这是Web的未来。 此外,Angular.js还拥有一个庞大的社区和
大量的贡献者。
AngularJS 的缺陷
Discoures开发者
Evil Trout在自己的博客上对比了这两个框架。Evil Trout列举了AngularJS的一些缺陷:
“简单”的陷阱
现在我知道现在为什么AngularJS势头越来越大:因为它很简单。一个精简了许多高级概念与实现的框架,会因此变得更容易学习。如果要我给这些框架排个名次的话,Angularjs大概是介于Backbone和Ember之间。 如果您的应用程序是简单,那么使用简单的框架想来也是极好。但如果你是要构建大规模的应用程序的话就要谨慎选择了,而且要进行长期的维护。 比起AngularJS,Ember有更多需要学习的概念。当你由于Ember的复杂性放弃它的时候,请考虑为什么开发人员添加了这些所谓多余的东西。事物的存在总有它的道理。 你会发现Ember是一个充满概念与实用的工具集,如果你想建立一个庞大的、可维护的应用程序。它的API侧重于通过一个健全的方式帮助你结构代码。Ember有一些AngularJS框架没有的理念。
AngularJS的Model层
AngularJS吹捧自己为MVC框架,或者是MVW(Model View Whatever)框架。 很明显的,AngularJS的View层是:让你通过
ng-*
属性和handlebars风格的
{{variable}}
表达式来标注HTML文档。Controller层是JavaScript类通过
ng-controller
属性的元素绑定DOM元素。 特别是,如果你有一个Server端的MVC背景, AngularJS的Model层该是什么样的,这点并不明确。而且在AngularJS中,并没有标准来定义了一个模型应该是Model基类,还是一个component(组件)或interface(接口)。 在一个AngularJS的Controller层中,有一个
$scope
对象。所有附加的数据通过它绑定在你的HTML模板:
function SomeCtrl($scope) {
$scope.countries = ['can', 'usa', 'fra', 'jap'];
$scope.user = {name: "Evil Trout"};
$scope.age = 34;
// 我们的模板现在可以渲染 {{age}}, {{user.name}} 和很多国家了!
}
根据AngularJS的文档,在AngularJS中所有声明在
$scope上
的对象都是一个Model,不仅仅对象和数组是Model,连primitive也是! 在模板中,AngularJS提供给你所需的工具来管理单一数据来源。这是一个叫数据绑定的概念。如果我们创建了一个模板中有一个AngularJS表达式
{{age}}
,我们说这绑定于
$scope.age
这个Model。如果你在一个模板中多处书写
{{age}}
,并在Controller层中执行
$scope.age = 40
,所有绑定的值都会同时更新。 然而,如果你真正想表达单一数据来源,你所需要的是在二级的数据绑定,就在你的数据Model本身。换句话说,AngularJS的短板在于:只允许将数据绑定在$scope和模板之间,而不是在Javascript代码的结构中。 在Ember中,所有Model扩展在
Ember.Object
基类上。因此你能够在Model内部声明对象之间的关系。例如:
App.Room = Ember.Object.extend({
area: function() {
return this.get('width') * this.get('height');
}.property('width', 'height')
});
在这里,我们已经创建了一个名为
Room
的Model。我们已经声明
area
为计算属性。
property
通知Ember,
Room
的
area
属性取决于其
width
和
height
属性。 创建一个Room Model的实例还是很容易的:
var room = App.Room.create({width: 10, height: 5});
现在我们可以创建一个模板:
<p>Room:</p>
<p>{{width}} ft.</p>
<p>{{height}} ft.</p>
<p>{{area}} sq ft.</p>
相应的Ember会正确地渲染这些属性。在这种情况下,area不得不与 width和 height同步更新。如果这两个属性的变化,area将自动更新。 因为在AngularJS中,Model都是普通的Javascript对象,AngularJS没有计算属性。但是,您可以在相应的对象上通过函数模拟来实现:
var Room = function(args) {
this.width = args.width;
this.height = args.height;
}
Room.prototype.area = function() {
return this.width * this.height;
}
要访问我们的房间的面积,你必须添加一组括号area()调用:
<p>Room:</p>
<p>{{width}} ft.</p>
<p>{{height}} ft.</p>
<p>{{area()}} sq ft.</p>
这显示了Ember和AngularJS之间的关键区别。Ember遵循统一访问原则 。 在一个Ember模板中,无论你所访问的是计算属性还是primitive,表达方式看上去是一样的。而在AngularJS中,必须明确区分函数。 这可能会导致可维护性的恶梦。在一个大型软件项目中,随着时间的推移,你将不可避免地要迭代原有的代码。在Ember中,你可以轻而易举地做到这点;而在AngularJS你就不得不更新每一个绑定于这个Model的模板。
使用getter和setter
这个相关的权衡是值得讨论的,你可能已经注意到,在Ember中,为了访问一个Model的属性,你必须使用
getter
和
setter
。这意味着需要一点点额外的代码,但你收获的是和模板中JavaScrip一样的好处:用函数替换primitive可以工作! 使用
getter
和
setter
的另一个好处是可以保证安全。思考下面的代码:
console.log(room.inhabitant.name);
如果
inhabitant
不存在,会发生什么事? 你会得到一个JavaScript错误。而在Ember中,你会得到
undefined
返回值,这使得你更容易编写健壮的代码。
// 输出 undefined
console.log(room.get('inhabitant.name'));
复用对象实例
AngularJS相比Ember来说更难复用对象实例。例如在Ember模板中,可以通过
{{linkTo}}
helper链接到另一个路由:
<ul>
{{#each user in users}}
<li>{{linkTo 'users.show' user}}Show {{username}}{{/linkTo}}</li>
{{/each}}
</ul>
这里,我们遍历一个用户列表,并创建一个链接。当你的鼠标悬停在链接上,如果你的路由配置正确,你会看到一些
/users/show/123
之类的文字。 然而,你点击链接时,Ember实际上通过你所配置的其他路由到达相关的用户页面。 Ember的路由器足够聪明,如果这个用户的id已经在内存中,Ember不会重复解析。而在AngularJS中,每次访问路由,它传递一个id并在Controller层进行解析。 一个长存的浏览器应用程序的巨大优势之一是可以重用的对象,就像上面那个用户导航的例子。AngularJS并没有遵循这一理念,它鼓励你扔掉它,然后再次找到它(可能是从Server端再次获取数据!)。