天天看點

Angular.js vs Ember.js

Discourse的推出在整個社群賺足了眼球。Discourse選擇Ember.JS作為前端MVC架構,其開發者Robin Ward 寫了部落格分享選擇Ember.js的理由 最近Quora網站上也有人提問, Angular.js Ember.js

,哪個JavaScript架構更好?

這個問題得到了熱烈的回應,兩個架構的開發者都參與了。

Angular.js vs Ember.js

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也參與了讨論。

是基于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>{{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連結到另一個路由:

{{#each user in users}}

  <li>{{linkTo 'users.show' user}}Show {{username}}{{/linkTo}}</li>

{{/each}}

這裡,我們周遊一個使用者清單,并建立一個連結。當你的滑鼠懸停在連結上,如果你的路由配置正确,你會看到一些

/users/show/123

之類的文字。 然而,你點選連結時,Ember實際上通過你所配置的其他路由到達相關的使用者頁面。

Ember的路由器足夠聰明,如果這個使用者的id已經在記憶體中,Ember不會重複解析。而在AngularJS中,每次通路路由,它傳遞一個id并在Controller層進行解析。

一個長存的浏覽器應用程式的巨大優勢之一是可以重用的對象,就像上面那個使用者導航的例子。AngularJS并沒有遵循這一理念,它鼓勵你扔掉它,然後再次找到它(可能是從Server端再次擷取資料!)。

繼續閱讀