Think in AngularJS:對比jQuery和AngularJS的不一樣思惟模式

 

Think in AngularJS:對比jQuery和AngularJS的不一樣思惟模式

 

大漠窮秋前端

 

導言

 

stackoverflow上有一我的問了一個問題:若是我有jQuery背景,我應該如何切換到AngularJS的思惟模式?web

 

有一個回覆很是經典,得到了兩千多票。ajax

 

爲了讓國內開發者也能領略到其中的核心思想,現把這個問題和答案翻譯出來供你們參考。編程

 

Question

 

假設我已經熟悉瞭如何使用jQuery來開發客戶端應用,我如今打算使用AngularJS。請描述一下有那些思惟模式方面的東西須要轉變嗎?下面是舉出一些具體的問題,用來幫助你回答個人這個問題:json

 

  1. 我應該以何種不一樣的方式來架構和設計客戶端web應用?最大的不一樣點是什麼?
  2. 我應該中止使用哪些東西;又應該開始使用哪些東西來替代?
  3. 服務端有沒有什麼須要考慮或者說須要約束的地方?

 

PS:我不想詳細對比jQuery和AngularJS之間的不一樣點。架構

 

Answer

 

1.不要預先設計頁面,而後再用DOM操做去修改它

 

在jQuery裏面,你會先設計好一個頁面,而後再讓它變成動態的。這是由於jQuery自己就是以混合使用的思路來設計的。基於這個簡單的前提,jQuery目前已經變得臃腫不堪。app

 

可是在AngularJS的世界中,你心中首先必須有總體架構,而後從零開始構建應用。而不是一開始的時候就去想:「我已經有了這樣一塊DOM結構,我想讓它作×××」,你必須首先思考你到底要完成什麼功能,而後再開始動手,而後再設計你的應用,最後再去設計你的視圖。框架

 

2.不要混合使用jQuery和AngularJS

 

相似地,不要一開始就抱有這樣的想法:jQuery能夠實現X、Y和Z,因此我只要在上面再覆蓋一層AngularJS,把模型和控制器加上便可。當你剛開始使用AngularJS的時候,這種想法實在誘人,這也是爲何我老是建議AngularJS新手完全拋棄jQuery的緣由,直到他們習慣了以「Angular風格」去作事爲止。ide

 

在這裏,以及在郵件列表裏面,我看到過不少這種精心設計的解決方案,其中包含150或者200行代碼的jQuery插件,而後他們再用一大堆回調函 數和$apply把這些插件粘到AngularJS上,這種作法很是複雜並且使人困惑不已;可是,他們最終竟然能把這貨弄跑起來了!這裏的問題在於,在大 多數狀況下,只須要不多的AngularJS代碼就能夠把這些jQuery插件重寫一遍,而後全部事情都會忽然變得簡潔明瞭起來。函數

 

底線是:在解決問題的過程當中,首先「Think in AngularJS」(以AnguarJS的方式思考問題);若是你想不到解決方案,請求助於社區;若是在嘗試了全部這些方法以後還找不到簡單的解決方案,而後再求助於jQuery。可是,不要讓jQuery變成絆腳石,不然你永遠沒法真正掌握AngularJS。

 

3.保持以架構的角度思考

 

首先要明確一點,單頁面應用是一種應用,它們不是web頁面。因此,咱們須要像服務端開發者那樣去思考,而不是像客戶端開發者那樣思考。咱們必須思考如何把咱們的應用切分紅獨立的、可擴展的、可測試的組件。

 

那麼,你怎麼才能作到這一點呢?你應該如何以AngularJS的方式思考問題呢?下面是一些基本的原則,與jQuery作個對比。

 

假設有一個叫作「官方記錄」(official record)的視圖

 

在jQuery裏面,咱們會用編程的方式來修改視圖,咱們會像下面這樣用ul標籤來定義一個下拉列表:

 

<ul class="main-menu">
    <li class="active">
        <a href="#/home">Home</a>
    </li>
    <li>
        <a href="#/menu1">Menu 1</a>
        <ul>
            <li><a href="#/sm1">Submenu 1</a></li>
            <li><a href="#/sm2">Submenu 2</a></li>
            <li><a href="#/sm3">Submenu 3</a></li>
        </ul>
    </li>
    <li>
        <a href="#/home">Menu 2</a>
    </li>
</ul>

 

在jQuery裏面,咱們的應用邏輯中會像下面這行代碼同樣來建立這個下拉列表:

 

$('.main-menu').dropdownMenu();

 

若是咱們僅僅看視圖代碼,咱們沒法馬上發現它有什麼功能。對於小型的應用來講,這樣作還算能夠。可是對於大型應用來講,很快就會變得混亂並難以維護。

 

然而,在AngularJS中,視圖是一種功能,它是基於視圖的「官方記錄」。咱們的ul聲明看起來就像下面這樣:

 

<ul class="main-menu" dropdown-menu>
    ...
</ul>

 

兩種實現方式的效果徹底相同,可是在AngularJS的版本中,每個看到模板的人都知道它在作什麼。 無論何時開發團隊有新人進來,她看到這種代碼以後就會當即明白,存在一個叫作dropdownMenu的指令,這個指令負責操控這個視圖。她憑直覺就能夠知道答案,而沒有必要查看任何代碼。視圖自己已經告訴了咱們這裏會發生什麼。這樣就更加清晰了。

 

AngularJS新手常常會問這樣的問題:我怎麼才能找出某種類型的全部超連接,而後在上面加上指令呢?咱們會這樣回答他:你不該該這麼作。而後 他老是一副驚呆了樣子。你不該該這麼作的緣由是:這是一種半jQuery半AngularJs式的思惟方式,這不科學。用這種方式思考問題永遠得不到很好 的結果。你看到的應該是「官方記錄」。除了指令以外(不僅包括如下代碼),你永遠、永遠、永遠不該該去修改DOM。同時,指令會用在視圖上,這樣一來思路就清晰了。

 

記住:不要先設計,而後編寫標籤。你必須先架構,而後去設計。

 

數據綁定

 

到目前爲止,這是AngularJS最讚的特性,利用這一特性能夠省掉我在上一小節中提到的大量DOM操做代碼。AngularJS會自動爲你刷新視圖,因此不須要你本身去作這件事!在jQuery中,咱們會響應事件而後刷新頁面內容。示例以下:

 

$.ajax({
  url: '/myEndpoint.json',
  success: function ( data, status ) {
    $('ul#log').append('<li>Data Received!</li>');
  }
});

 

對應的視圖代碼以下:

 

<ul class="messages" id="log">
</ul>

 

這種方式除了會把注意點混在一塊兒以外,還有我在前面所提到的思惟模式問題。可是,更加劇要的一點是,這樣作咱們必須手動引用並更新DOM節點。同 時,若是咱們想刪掉一個log對象,咱們必須針對DOM從新進行編碼。這樣一來咱們如何脫離DOM來測試這些邏輯呢?同時,若是咱們要修改顯示效果又應該 怎麼作呢?

 

這樣有點兒亂,代碼既瑣碎又脆弱。可是在AngularJS中,咱們能夠這樣作:

 

$http( '/myEndpoint.json' ).then( function ( response ) {
    $scope.log.push( { msg: 'Data Received!' } );
});

 

而後咱們的視圖代碼是這樣的:

 

<ul class="messages">
    <li ng-repeat="entry in log">{{ entry.msg }}</li>
</ul>

 

對於上面所提的刪除log對象這個問題,咱們能夠把視圖寫成這樣:

 

<div class="messages">
    <div class="alert" ng-repeat="entry in log">
        {{ entry.msg }}
    </div>
</div>

 

這裏咱們用Bootstrap的alert塊替換了無序列表。而且咱們永遠不須要修改控制器代碼!同時更重要的是,不管什麼時候或者何地更新了log對象,視圖都會自動**刷新。高貴優雅!

 

雖然我沒有在這裏展現,其實數據綁定操做是雙向的。因此,在視圖中也能夠編輯log信息,只要這樣作便可:<input ng-model="entry.log"/>。另外還有更多驚喜。

 

區分數據模型層

 

在jQuery中,DOM有相似數據模型的意味。可是在AngularJS中,咱們有一個獨立的數據模型層,咱們能夠按照本身的想法管理它,它和視 圖層徹底獨立。對於前面例子中的數據綁定操做來講,這一點頗有用,而且保持了注意點分離的原則,同時還能夠引入更強的測試功能。在其它不少解答中都提到了 這一點,因此這裏我就再也不贅述了。

 

注意點分離

 

以上全部一切都是爲了實現這樣一個遠大的目標:讓你的注意點保持分離。你的視圖的角色是展現「官方記錄」所能進行的全部操做(絕大部分);你的數據 模型用來表明你的數據;你還有一個service層用來執行可複用的任務;你進行DOM操做並把指令混入到視圖中;最後你再用controller把全部 東西粘到一塊兒。在其它不少回覆裏面都提到了這一點,我惟一想要補充的一點是關於測試方面,在下面的小節中我會來討論它。

 

依賴注入

 

用來幫助咱們實現注意點分離的特性就是依賴注入。若是你是從服務端語言轉過來的(例如從Java或者PHP),你可能對這個概念已經至關熟悉,可是若是你是一個前端仔,從jQuery轉過來的,你可能會以爲這種概念很愚蠢、不少餘、並且很裝逼。但事實並不是如此。

 

從大的層面上講,DI意味着你能夠自由地聲明組件,而後在其它組件中,你能夠請求所聲明組件的實例,而後你就能夠得到它。你沒有必要知道加載順序、文件路徑,以及諸如此類的東西。這種概念的強大能量可能不是那麼顯而易見,這裏我只舉一個(通用的)例子:測試。

 

比方說在咱們的應用中,根據應用的狀態,咱們須要經過一個REST API請求一個服務端的存儲實現,以及本地的存儲實現。當對咱們的controller進行測試的時候,咱們並不想和服務端進行通信,畢竟咱們正在測試的是controller而不是其它東西。咱們能夠僅僅添加一個虛擬的同名service做爲前面所說的自定義組件,而後注射器將會保證controller可以自動得到這個虛擬的服務,咱們的controller不會知道它們之間有什麼不一樣,也沒有必要知道。

 

關於測試再多說一點...

 

4.測試驅動開發---永遠

 

這裏的內容是關於架構方面的,實際上應該屬於第三小節,可是這塊內容極其重要,因此我把它獨立成了一個單獨的小節。

 

在你所見過、用過,或者寫過的全部jQuery插件中,它們有多少個帶有完整的測試用例?不是不少,由於jQuery不是太鳥這個原則。可是AngularJS很是看重這一點。

 

在jQuery中,惟一可以進行測試的方式一般是在一個sample/demo頁面上建立獨立的組件,經過這個頁面咱們能夠進行DOM操做相關的測 試。因此,這樣一來咱們必須獨立開發一個組件,而後再把它集成到咱們的應用中去。好麻煩!在使用jQuery進行開發的時候,消耗的時間太多了,這是由於 咱們選擇了迭代的方式,而不是選擇測試驅動開發的方式。如此一來,誰又能責怪咱們呢?

 

可是,在AngularJS中,因爲咱們分離了注意點,因此咱們能夠用迭代的方式進行測試驅動開發!例如,比方說咱們須要一個超級簡單的指令,用來在菜單中顯示當前的路由是什麼。咱們能夠這樣在視圖中聲明所須要的東西:

 

<a href="/hello" when-active>Hello</a>

 

好,如今咱們來編寫一個單元測試:

 

it( 'should add "active" when the route changes', inject(function() {
    var elm = $compile( '<a href="/hello" when-active>Hello</a>' )( $scope );

    $location.path('/not-matching');
    expect( elm.hasClass('active') ).toBeFalsey();

    $location.path( '/hello' );
    expect( elm.hasClass('active') ).toBeTruthy();
}));

 

咱們運行這個單元測試,確認它是否會失敗。而後咱們再來編寫指令:

 

.directive( 'whenActive', function ( $location ) {
    return {
        scope: true,
        link: function ( scope, element, attrs ) {
            scope.$on( '$routeChangeSuccess', function () {
                if ( $location.path() == element.attr( 'href' ) ) {
                    element.addClass( 'active' );
                }
                else {
                    element.removeClass( 'active' );
                }
            });
        }
    };
});

 

如今,咱們的測試運行經過了,而且菜單的行爲符合了咱們的預期。這樣一來,咱們的開發既是可迭代的,也是測試驅動的。碉堡了。

 

5.從概念上說,指令並不是打包好的jQuery

 

你常常會聽到「只能在指令中操做DOM」之類的言論。這是必須的。請慎重對待這一原則。

 

咱們再來稍微深刻一點...

 

有一些指令只是用來裝飾一下視圖裏面已經存在的內容(想一想ngClass),有時候也會直接進行一些DOM操做,而後就沒有而後了。可是,像 「widget」(小組件)這樣帶有模板的指令,它一樣須要遵照注意點分離的原則。也就是說,模板自身一樣須要保持很強的獨立性,獨立於link和 controller函數的具體實現。

 

AngularJS內置了完整的工具,讓實現這一點很是容易;咱們可使用ngClass指令來動態更新CSS樣式類;ngBind能夠用來作雙向 數據綁定;ngShow和ngHide能夠以編程的方式來顯示或者隱藏元素;諸如此類還有不少。咱們也能夠導入咱們本身所編寫的指令。換句話說,咱們能夠 實現各類絢麗的效果而不須要進行DOM操做。進行的DOM操做越少,指令測試起來就越容易、設置樣式就越容易、在將來修改起來也會越容易、而且可複用性和 可分發性也會更好。

 

我看到不少AngularJS新手把指令當成容納各類jQuery代碼的場所。換句話說,他們的想法是:「既然我不能在控制器裏面作DOM操做,那我就把DOM操做相關的代碼放到指令裏面好了」。這種作法確實是好一些了,可是一般仍是是錯誤的

 

思考一下咱們在第三小節裏面所編寫的logger應用。即便咱們把相關的操做放到了指令裏面,咱們仍是用一種「AngularJS的方式」來實現了 它。它仍然沒有作任何DOM操做!在不少狀況下DOM操做是必須的,可是這種狀況比你想象的要少得多!當你在應用裏面的任何地方進行DOM操做以前,請問 問本身,是否是真的必需要這樣作。頗有可能存在更好的實現方式。

 

下面是一個小例子,用來講明我常常看到的一種模式。咱們須要一個開關型的按鈕。(注意:這個例子的代碼有點裝逼,而且有點冗長,只是爲了用來表明更加複雜一些的例子,這些例子一般是以與此相同的方式來解決的。)

 

.directive( 'myDirective', function () {
    return {
        template: '<a class="btn">Toggle me!</a>',
        link: function ( scope, element, attrs ) {
            var on = false;

            $(element).click( function () {
                if ( on ) {
                    $(element).removeClass( 'active' );
                }
                else {
                    $(element).addClass( 'active' );
                }

                on = !on;
            });
        }
    };
});

 

這段代碼裏面有不少錯誤的地方。

 

第一,jQuery歷來就不是必須的。咱們這裏要實現的東西實際上徹底不須要jQuery!

 

第二,即便咱們已經在頁面上引入了jQuery,也沒有必要在這裏去使用;對於沒有使用jQuery的項目,咱們能夠簡單地使用angular.element,這樣一來咱們的組件一樣可以很好地運行。

 

第三,假設這裏必須使用jQuery咱們的指令才能運行,jqLite(angular.element)老是會自動使用jQuery,若是jQuery已經加載了話!因此咱們不須要使用$,咱們只要使用angular.element就能夠了。

 

第四,與第三點相似,jqLite元素沒有必要使用$來進行包裝,傳遞給link函數的element已是一個jQuery元素了!

 

還有第五點,這一點咱們在前面的小節中沒有提到,那就是咱們爲何要把模板相關的內容混合在咱們的代碼邏輯裏面?

 

以上指令能夠重寫成下面這樣(即便對於很是複雜的狀況一樣能夠改寫!),改寫以後代碼極其簡單:

 

.directive( 'myDirective', function () {
    return {
        scope: true,
        template: '<a class="btn" ng-class="{active: on}" ng-click="toggle()">Toggle me!</a>',
        link: function ( scope, element, attrs ) {
            scope.on = false;

            scope.toggle = function () {
                scope.on = !$scope.on;
            };
        }
    };
});

 

再說一次,模板相關的內容位於template中,因此你(或者你的用戶)能夠簡單地切換它,從而能夠知足任何須要的樣式要求,同時永遠不須要去修改代碼邏輯。可複用性---嘭!

 

這樣改寫以後還會帶來其它好處,好比測試---這是必須的!無論模板裏面是什麼內容,指令內部的API永遠不須要修改,這樣一來重構就很是簡單了。你能夠隨意修改模板的內容而沒有必要去理睬指令。同時不管你修改了什麼內容,你的測試依然可以運行經過。

 

w00t!

 

好吧,若是指令並不是jQuery函數之類的集合,那麼它們是什麼呢?實際上指令是HTML擴展。若是HTML沒法作到你想實現的某件事情,你就本身編寫一個指令,而後再去使用這個指令,好像它就是HTML的一部分同樣。

 

換句話說,若是AngularJS沒有內置支持某件事情,請思考一下你的團隊應該怎麼樣去實現它,參照ngClick,ngClass等指令的作法。

 

小結

 

不要使用jQuery。最好不要引入它。它只會拖你的後腿。當你遇到一個問題,而這個問題你知道如何使用jQuery去解決,那麼在你使用$以前, 請思考一下如何以AngularJS的方式去解決它。若是你不知道,去問別人!最好的解決方式十有八九不須要使用jQuery,若是你用jQuery的方 式來解決,最終會給你帶來更多工做量。

 

其它相關內容:

 

一、OReilly的《AngularJS》已由電子工業出版社出版

 

http://damoqiongqiu.iteye.com/blog/1965167

 

二、《AngularJS》5個實例詳解Directive(指令)機制

 

http://damoqiongqiu.iteye.com/blog/1917971

 

三、AngularJS表單基礎

 

http://damoqiongqiu.iteye.com/blog/1920191

 

四、AngularJS Form 進階:遠程校驗和自定義輸入項

 

http://damoqiongqiu.iteye.com/blog/1920993

 

五、AngularJS:在Windows上安裝Yeoman

 

http://damoqiongqiu.iteye.com/blog/1885371

 

六、對比Angular/jQueryUI/Extjs:沒有一個框架是萬能的

 

http://damoqiongqiu.iteye.com/blog/1922004

 

七、使用JsTestDriver實現JavaScript單元測試

 

http://damoqiongqiu.iteye.com/blog/1924415

 

八、JavaScript單元測試系列二:將Jasmine集成到JsTestDriver

 

http://damoqiongqiu.iteye.com/blog/1925974

相關文章
相關標籤/搜索