AngularJS開發人員最常犯的10個錯誤
簡介
AngularJS是目前最為活躍的Javascript框架之一,AngularJS的目標之一是簡化開發過程,這使得AngularJS非常善於構建小型app原型,但AngularJS對於全功能的客戶端應用程式同樣強大,它結合了開發簡便,特性廣泛和出眾的效能,使其被廣泛使用。然而,大量使用也會產生諸多誤區。以下這份列表摘取了常見的一些AngularJS的錯誤用法,尤其是在app開發過程中。
1. MVC目錄結構
AngularJS,直白地說,就是一個MVC框架。它的模型並沒有像backbone.js框架那樣定義的如此明確,但它的體系結構卻恰如其分。當你工作於一個MVC框架時,普遍的做法是根據檔案型別對其進行歸類:
123456789101112131415 | templates/_login.html_feed.htmlapp/app.jscontrollers/LoginController.jsFeedController.jsdirectives/FeedEntryDirective.jsservices/LoginService.jsFeedService.jsfilters/CapatalizeFilter.js |
看起來,這似乎是一個顯而易見的結構,更何況Rails也是這麼幹的。然而一旦app規模開始擴張,這種結構會導致你一次需要開啟很多目錄,無論你是使用sublime,Visual Studio或是Vim結合Nerd Tree,你都會投入很多時間在目錄樹中不斷地滑上滑下。
與按照型別劃分檔案不同,取而代之的,我們可以按照特性劃分檔案:
JavaScript12345678910111213 | app/app.jsFeed/_feed.htmlFeedController.jsFeedEntryDirective.jsFeedService.jsLogin/_login.htmlLoginController.jsLoginService.jsShared/CapatalizeFilter.js |
這種目錄結構使得我們能夠更容易地找到與某個特性相關的所有檔案,繼而加快我們的開發進度。儘管將.html和.js檔案置於一處可能存在爭議,但節省下來的時間更有價值。
2. 模組
將所有東西都一股腦放在主模組下是很常見的,對於小型app,剛開始並沒有什麼問題,然而很快你就會發現坑爹的事來了。
JavaScript1234567 | varapp=angular.module('app',[]);app.service('MyService',function(){//service code});app.controller('MyCtrl',function($scope,MyService){//controller code}); |
在此之後,一個常見的策略是對相同型別的物件歸類。
JavaScript1234567891011 | varservices=angular.module('services',[]);services.service('MyService',function(){//service code});varcontrollers=angular.module('controllers',['services']);controllers.controller('MyCtrl',function($scope,MyService){//controller code});varapp=angular.module('app',['controllers','services']); |
這種方式和前面第一部分所談到的目錄結構差不多:不夠好。根據相同的理念,可以按照特性歸類,這會帶來可擴充套件性。
JavaScript12345678 | varsharedServicesModule=angular.module('sharedServices',[]);sharedServices.service('NetworkService',function($http){});varloginModule=angular.module('login',['sharedServices']);loginModule.service('loginService',function(NetworkService){});loginModule.controller('loginCtrl',function($scope,loginService){});varapp=angular.module('app',['sharedServices','login']); |
當我們開發一個大型應用程式時,可能並不是所有東西都包含在一個頁面上。將同一類特性置於一個模組內,能使跨app間重用模組變得更容易。
3. 依賴注入
依賴注入是AngularJS最好的模式之一,它使得測試更為簡單,並且依賴任何指定物件都很明確。AngularJS的注入方式非常靈活,最簡單的方式只需要將依賴的名字傳入模組的function中即可:
JavaScript1234567 | varapp=angular.module('app',[]);app.controller('MainCtrl',function($scope,$timeout){$timeout(function(){console.log($scope);},1000);}); |
這裡,很明顯,MainCtrl依賴$scope和$timeout。
直到你準備將其部署到生產環境並希望精簡程式碼時,一切都很美好。如果使用UglifyJS,之前的例子會變成下面這樣:
JavaScript12 | varapp=angular.module("app",[]);app.controller("MainCtrl",function(e,t){t(function(){console.log(e)},1e3)}) |
現在AngularJS怎麼知道MainCtrl依賴誰?AngularJS提供了一種非常簡單的解決方法,即將依賴作為一個數組傳入,陣列的最後一個元素是一個函式,所有的依賴項作為它的引數。
JavaScript12345 | app.controller('MainCtrl',['$scope','$timeout',function($scope,$timeout){$timeout(function(){console.log($scope);},1000);}]); |
這樣做能夠精簡程式碼,並且AngularJS知道如何解釋這些明確的依賴:
JavaScript1 | app.controller("MainCtrl",["$scope","$timeout",function(e,t){t(function(){console.log(e)},1e3)}]) |
3.1 全域性依賴
在編寫AngularJS程式時,時常會出現這種情況:某個物件有一個依賴,而這個物件又將其自身繫結在全域性scope上,這意味著在任何AngularJS程式碼中這個依賴都是可用的,但這卻破壞了依賴注入模型,並會導致一些問題,尤其體現在測試過程中。
使用AngularJS可以很容易的將這些全域性依賴封裝進模組中,所以它們可以像AngularJS標準模組那樣被注入進去。
Underscrore.js是一個很讚的庫,它可以以函式式的風格簡化Javascript程式碼,通過以下方式,你可以將其轉化為一個模組:
JavaScript12345678910111213 | varunderscore=angular.module('underscore',[]);underscore.factory('_',function(){returnwindow._;//Underscore must already be loaded on the page});varapp=angular.module('app',['underscore']);app.controller('MainCtrl',['$scope','_',function($scope,_){init=function(){_.keys($scope);}init();}]); |
這樣的做法允許應用程式繼續以AngularJS依賴注入的風格進行開發,同時在測試階段也能將underscore交換出去。
這可能看上去十分瑣碎,沒什麼必要,但如果你的程式碼中正在使用use strict(而且必須使用),那這就是必要的了。
4. 控制器膨脹
控制器是AngularJS的肉和土豆,一不小心就會將過多的邏輯加入其中,尤其是剛開始的時候。控制器永遠都不應該去操作DOM,或是持有DOM選擇器,那是我們需要使用指令和ng-model的地方。同樣的,業務邏輯應該存在於服務中,而非控制器。
資料也應該儲存在服務中,除非它們已經被繫結在$scope上了。服務本身是單例的,在應用程式的整個生命週期都存在,然而控制器在應用程式的各狀態間是瞬態的。如果資料被儲存在控制器中,當它被再次例項化時就需要重新從某處獲取資料。即使將資料儲存於localStorage中,檢索的速度也要比Javascript變數慢一個數量級。
AngularJS在遵循單一職責原則(SRP)時執行良好,如果控制器是檢視和模型間的協調者,那麼它所包含的邏輯就應該儘量少,這同樣會給測試帶來便利。
5. Service vs Factory
幾乎每一個AngularJS開發人員在初學時都會被這些名詞所困擾,這真的不太應該,因為它們就是針對幾乎相同事物的語法糖而已!
以下是它們在AngularJS原始碼中的定義:
JavaScript123456789 | functionfactory(name,factoryFn){returnprovider(name,{$get:factoryFn});}functionservice(name,constructor){returnfactory(name,['$injector',function($injector){return$injector.instantiate(constructor);}]);} |
從原始碼中你可以看到,service僅僅是呼叫了factory函式,而後者又呼叫了provider函式。事實上,AngularJS也為一些值、常量和裝飾提供額外的provider封裝,而這些並沒有導致類似的困惑,它們的文件都非常清晰。
由於service僅僅是呼叫了factory函式,這有什麼區別呢?線索在$injector.instantiate:在這個函式中,$injector在service的建構函式中建立了一個新的例項。
以下是一個例子,展示了一個service和一個factory如何完成相同的事情:
JavaScript123456789101112131415 | varapp=angular.module('app',[]);app.service('helloWorldService',function(){this.hello=function(){return"Hello World";};});app.factory('helloWorldFactory',function(){return{hello:function(){return"Hello World";}}}); |
當helloWorldService或helloWorldFactory被注入到控制器中,它們都有一個hello方法,返回”hello world”。service的建構函式在宣告時被例項化了一次,同時factory物件在每一次被注入時傳遞,但是仍然只有一個factory例項。所有的providers都是單例。
既然能做相同的事,為什麼需要兩種不同的風格呢?相對於service,factory提供了更多的靈活性,因為它可以返回函式,這些函式之後可以被新建出來。這迎合了面向物件程式設計中