您好,登錄后才能下訂單哦!
這篇文章主要介紹了AngularJS開發(fā)者常犯的錯(cuò)誤有哪些,具有一定借鑒價(jià)值,感興趣的朋友可以參考下,希望大家閱讀完這篇文章之后大有收獲,下面讓小編帶著大家一起了解一下。
AngularJS是如今***的JS框架之一,簡化開發(fā)過程是它的目標(biāo)之一,這使得它非常適合于元型較小的apps的開發(fā),但也擴(kuò)展到具有全部特征的客戶端應(yīng)用的開發(fā)。易于開發(fā)、較多的特征及較好的效果導(dǎo)致了較多的應(yīng)用,伴隨而來的是一些陷阱。本文列舉了AngularJS的一些共同的易于也問題的地方,尤其是在開發(fā)一個(gè)app的時(shí)候。
AngularJS是一個(gè)缺乏較好的term的MVC框架,其models不像backbone.js中那樣做為一個(gè)框架來定義,但其結(jié)構(gòu)模式仍匹配的較好。當(dāng)在一個(gè)MVC框架中作業(yè)時(shí),基于文件類型將文件組合在一起是其共同的要求:
templates/ _login.html _feed.html app/ app.js controllers/ LoginController.js FeedController.js directives/ FeedEntryDirective.js services/ LoginService.js FeedService.js filters/ CapatalizeFilter.js
這樣的布局, 尤其是對那些有 Rails 背景的人來說, 看起來挺合理. 可是當(dāng) app 變得越來越龐大的時(shí)候, 這樣的布局結(jié)構(gòu)會(huì)導(dǎo)致每次都會(huì)打開一堆文件夾. 無論你是用 Sublime, Visual Studio, 還是 Vim with Nerd Tree, 每次都要花上很多時(shí)間滑動(dòng)滾動(dòng)條瀏覽這個(gè)目錄樹來查找文件.
如果我們根據(jù)每個(gè)文件隸屬的功能模塊來對文件分組, 而不是根據(jù)它隸屬的層:
app/ app.js Feed/ _feed.html FeedController.js FeedEntryDirective.js FeedService.js Login/ _login.html LoginController.js LoginService.js Shared/ CapatalizeFilter.js
那么查找某個(gè)功能模塊的文件就要容易得多, 自然可以提高開發(fā)的速度. 也許把 html 文件跟 js 文件放在混合放在一起做法不是每個(gè)人都能認(rèn)同. 但是起碼它省下寶貴的時(shí)間.
一開始就將主模塊中所有子模塊展示出來是通常的做法。但是開始做一個(gè)小應(yīng)用還好,但是做大了就不好管理了。
var app = angular.module('app',[]);app.service('MyService', function(){ //service code});app.controller('MyCtrl', function($scope, MyService){ //controller code});
一個(gè)比較好的辦法是將相似類型的子模塊分組:
var services = angular.module('services',[]);services.service('MyService', function(){ //service code});var controllers = angular.module('controllers',['services']);controllers.controller('MyCtrl', function($scope, MyService){ //controller code});var app = angular.module('app',['controllers', 'services']);
這個(gè)方法與上面那個(gè)方法效果差不多,但是也不很大。運(yùn)用要分組的思想將使工作更容易。
var sharedServicesModule = angular.module('sharedServices',[]); sharedServices.service('NetworkService', function($http){}); var loginModule = angular.module('login',['sharedServices']); loginModule.service('loginService', function(NetworkService){}); loginModule.controller('loginCtrl', function($scope, loginService){}); var app = angular.module('app', ['sharedServices', 'login']);
當(dāng)創(chuàng)建一個(gè)大的應(yīng)用時(shí),所有模塊可能不會(huì)放在一頁里,但是將模塊根據(jù)類型進(jìn)行分組將使模塊的重用能力更強(qiáng)。
依賴注入是AngularJS最棒的模式之一。它使測試變得更加方便,也讓它所依賴的對象變的更加清楚明白。AngularJS 對于注入是非常靈活的。一個(gè)最簡單的方式只需要為模塊將依賴的名字傳入函數(shù)中:
var app = angular.module('app',[]);app.controller('MainCtrl', function($scope, $timeout){ $timeout(function(){ console.log($scope); }, 1000);});
這里,很清楚的是MainCtrl依賴于$scope和$timeout。
直到你準(zhǔn)備投入生產(chǎn)并壓縮你的代碼。使用UglifyJS,上面的例子會(huì)變成:
var app=angular.module("app",[]); app.controller("MainCtrl",function(e,t){t(function(){console.log(e)},1e3)})
現(xiàn)在AngularJS怎么知道MainCtrl依賴什么?AngularJS提供了一個(gè)非常簡單的解決方案:把依賴作為一個(gè)字符串?dāng)?shù)組傳遞,而數(shù)組的***一個(gè)元素是一個(gè)把所有依賴作為參數(shù)的函數(shù)。
app.controller('MainCtrl', ['$scope', '$timeout', function($scope, $timeout){ $timeout(function(){ console.log($scope); }, 1000);}]);
接下來在壓縮的代碼中AngularJS也可以知道如何找到依賴:
app.controller("MainCtrl",["$scope","$timeout",function(e,t){t(function(){console.log(e)},1e3)}])
通常在寫AngularJS應(yīng)用時(shí)會(huì)有一個(gè)對象作為依賴綁定到全局作用域中。這意味著它在任何AngularJS的代碼中都可用,但這打破了依賴注入模型同時(shí)帶來一些問題,特別是在測試中。
AngularJS把這些全局變量封裝到模塊中,這樣它們可以像標(biāo)準(zhǔn)AngularJS模塊一樣被注入。
Underscore.js是很棒的庫,它把Javascript代碼簡化成了函數(shù)模式,并且它可以被轉(zhuǎn)化成一個(gè)模塊:
var underscore = angular.module('underscore', []);underscore.factory('_', function() { return window._; //Underscore must already be loaded on the page});var app = angular.module('app', ['underscore']);app.controller('MainCtrl', ['$scope', '_', function($scope, _) { init = function() { _.keys($scope); } init();}]);
它允許應(yīng)用繼續(xù)用AngularJS依賴注入的風(fēng)格,也讓underscore在測試的時(shí)候被交換出來。
這或許看上去不重要,像是一個(gè)無關(guān)緊要的工作,但如果你的代碼正在使用use strict(應(yīng)該使用),那么這就變得有必要了。
控制器是AngularJS應(yīng)用中的肉和番茄。它很簡單,特別是開始的時(shí)候,在控制器中放入過多的邏輯??刂破鞑粦?yīng)該做任何DOM操作或者有DOM選擇器,這應(yīng)該由使用ngModel的指令(directives)做的事。同樣地,業(yè)務(wù)邏輯應(yīng)該在服務(wù)(services)中,而不是 控制器。
數(shù)據(jù)也應(yīng)該被存在服務(wù)(services)中,除非它已經(jīng)和$scope關(guān)聯(lián)。服務(wù)(services)是留存于整個(gè)應(yīng)用生命周期的個(gè)體,同時(shí)控制器在應(yīng)用各階段間都是暫態(tài)的。如果數(shù)據(jù)被存在控制器中,那么當(dāng)它被重新實(shí)例化的時(shí)候,就需要從其他地方抓取。即使數(shù)據(jù)被存儲(chǔ)在localStorage中,獲取數(shù)據(jù)也要比從Javascript變量中獲取要慢幾個(gè)數(shù)量級(jí)。
AngularJS在遵從簡單責(zé)任原則(SRP)時(shí)工作地***。如果控制器是視圖和模型的協(xié)調(diào)者,那么它擁有的邏輯應(yīng)該被最小化。這將使得測試變的更加簡單。
幾乎每一個(gè)剛接觸AngularJS的開發(fā)者,都會(huì)對這兩個(gè)東西產(chǎn)生困惑。 雖然它們(幾乎)實(shí)現(xiàn)了同樣的效果,但真的不是語法糖。
這里是它們在 AngularJS 源碼中的定義:
function factory(name, factoryFn) { return provider(name, { $get: factoryFn }); } function service(name, constructor) { return factory(name, ['$injector', function($injector) { return $injector.instantiate(constructor); }]); }
從源碼上看顯然 service 函數(shù)只是調(diào)用 factory 函數(shù),然后 factory 函數(shù)再調(diào)用 provider 函數(shù)。事實(shí)上,value、constant和decorator 也是 AngularJS 提供的對 provider 的封裝,但對它們使用場景不會(huì)有這種困惑,并且文檔描述也非常清晰。
那么Service 僅僅是單純的調(diào)用了一次 factory 函數(shù)嗎? 重點(diǎn)在 $injector.instantiate 中; 在這個(gè)函數(shù)里service會(huì)接收一個(gè)由$injector 使用new關(guān)鍵字去實(shí)例化的一個(gè)構(gòu)造器對象。(原文:with in this function $injector creates a new instance of the service's constructor function.)
下面是完成同樣功能的一個(gè)service和一個(gè)factory。
var app = angular.module('app',[]); app.service('helloWorldService', function(){ this.hello = function() { return "Hello World"; };}); app.factory('helloWorldFactory', function(){ return { hello: function() { return "Hello World"; } }});
當(dāng) helloWorldService 或者 helloWorldFactory中的任何一個(gè)注入到controller里面, 他們都有一個(gè)返回字符串"Hello World"的名稱為 hello方法。 這個(gè)service 的構(gòu)造函數(shù)只在聲明時(shí)被實(shí)例化一次,并且在這個(gè) factory 對象每次被注入時(shí)各種互相引用, 但這個(gè) factory還是只是被實(shí)例化了一次。 所有的 providers 都是單例的。
既然都完成同樣的功能,為什么會(huì)有這兩種格式存在?factory比service略微更靈活一些,因?yàn)樗鼈兛梢允褂?em>new關(guān)鍵字返回函數(shù)(原文:Factories offer slightly more flexibility than services because they can return functions which can then be new'd)。 在其他地方,從面向?qū)ο缶幊痰?em>工廠模式來說。 一個(gè)factory可以是一個(gè)用于創(chuàng)建其他對象的對象。
app.factory('helloFactory', function() { return function(name) { this.name = name; this.hello = function() { return "Hello " + this.name; }; }; });
這里有一個(gè)使用了前面提到的那個(gè)service和兩個(gè)factory的controller 的例子。需要注意的是 helloFactory 返回的是一個(gè)函數(shù),變量name的值是在對象使用new關(guān)鍵字的時(shí)候設(shè)置。
app.controller('helloCtrl', function($scope, helloWorldService, helloWorldFactory, helloFactory) { init = function() { helloWorldService.hello(); //'Hello World' helloWorldFactory.hello(); //'Hello World' new helloFactory('Readers').hello() //'Hello Readers' } init(); });
在剛?cè)腴T時(shí)候***只使用services.
Factory更加適用于當(dāng)你在設(shè)計(jì)一個(gè)需要私有方法的類的時(shí)候使用:
app.factory('privateFactory', function(){ var privateFunc = function(name) { return name.split("").reverse().join(""); //reverses the name }; return { hello: function(name){ return "Hello " + privateFunc(name); } };});
在這個(gè)例子中privateFactory含有一個(gè)不能被外部訪問的私有privateFunc函數(shù)。這種使用方式services也可以實(shí)現(xiàn),但是使用Factory代碼結(jié)構(gòu)顯得更加清晰。
Batarang 是用于開發(fā)和調(diào)試 AngularJS 應(yīng)用的一個(gè)優(yōu)秀的chrome瀏覽器插件。
Batarang 提供了模型瀏覽,可以查看Angular內(nèi)部哪些模型已經(jīng)綁定到作用域(scopes )??梢杂糜谛枰谶\(yùn)行時(shí)查看指令中的隔離作用域(isolate scopes)綁定的值。
Batarang 還提供了依賴關(guān)系圖。 對于引入一個(gè)未測試的代碼庫, 這個(gè)工具可以快速確定哪些services應(yīng)該得到更多的關(guān)注。
***, Batarang提供了性能分析。 AngularJS 雖然是高性能開箱即用, 但是隨著應(yīng)用自定義指令和復(fù)雜的業(yè)務(wù)邏輯的增長,有時(shí)候會(huì)感到頁面不夠流暢。使用 Batarang 的性能分析工具可以很方便的查看哪些functions 在digest 周期中占用了更多的時(shí)間。這個(gè)工具還可以顯示出整個(gè)監(jiān)控樹(full watch tree),當(dāng)頁面有太多的監(jiān)控器(watch)時(shí),這個(gè)功能就顯得有用了。
正如上文中提到的,在外部AngularJS是很不錯(cuò)的。因?yàn)樵谝粋€(gè)循環(huán)消化中需要進(jìn)行dirty檢查,一旦watcher的數(shù)目超過2,000,循環(huán)會(huì)出現(xiàn)很明顯的問題。(2,000僅是一個(gè)參考數(shù),在1.3版本中AngularJS對循環(huán)消化有更為嚴(yán)謹(jǐn)?shù)目刂?,關(guān)于這個(gè)Aaron Graye有較為詳細(xì)的敘述)
這個(gè)IIFE(快速響應(yīng)函數(shù))可輸出當(dāng)前本頁中的watcher的數(shù)目,只需將其復(fù)制到console即可查看詳情。IIFE的來源跟Jared關(guān)于StackOverflow的回答是類似的。
(function () { var root = $(document.getElementsByTagName('body')); var watchers = []; var f = function (element) { if (element.data().hasOwnProperty('$scope')) { angular.forEach(element.data().$scope.$$watchers, function (watcher) { watchers.push(watcher); }); } angular.forEach(element.children(), function (childElement) { f($(childElement)); }); }; f(root); console.log(watchers.length);})();
使用這個(gè),可以從Batarang的效率方面來決定watcher及watch tree的數(shù)目,可以看到在哪些地方顧在或哪些地方?jīng)]有改變的數(shù)據(jù)有一個(gè)watch。
當(dāng)有數(shù)據(jù)沒有變化時(shí),但在Angular中又想讓它成為模板,可以考慮使用bindonce.Bindonce在Angular中僅是一個(gè)可能使用模板的指令,但沒有增加watch的數(shù)目。
Javascript的基于原型的繼承和基于類的繼承在一些細(xì)微的方面是不同的。通常這不是問題,但是差別往往會(huì)在使用$scope時(shí)出現(xiàn)。在AngularJS中每一個(gè)$scope都從它的父$scope繼承過來,***層是$rootScope。($scope在指令中表現(xiàn)的有些不同,指令中的隔離作用域僅繼承那些顯式聲明的屬性。)
從父級(jí)那里分享數(shù)據(jù)對于原型繼承來說并不重要。不過如果不小心的話,會(huì)遮蔽父級(jí)$scope的屬性。
我們想在導(dǎo)航欄上呈現(xiàn)一個(gè)用戶名,然后進(jìn)入登陸表單。
<div ng-controller="navCtrl"> <span>{{user}}</span> <div ng-controller="loginCtrl"> <span>{{user}}</span> <input ng-model="user"></input> </div></div>
考你下:當(dāng)用戶在設(shè)置了ngModel的文本框中輸入了值,哪個(gè)模板會(huì)被更新?是navCtrl,loginCtrl還是兩者?
如果你選loginCtrl,那么你可能對原型繼承的機(jī)理比較了解了。當(dāng)尋找字面值時(shí),原型鏈并沒有被涉及。如果navCtrl要被更新的話,那么查找原型鏈?zhǔn)潜匾?。?dāng)一個(gè)值時(shí)對象的時(shí)候就會(huì)發(fā)生這些。(記住在Javascript中,函數(shù)、數(shù)組合對象都算作對象)
所以想要獲得期望的效果就需要在navCtrl上創(chuàng)建一個(gè)對象可以被loginCtrl引用。
<div ng-controller="navCtrl"> <span>{{user.name}}</span> <div ng-controller="loginCtrl"> <span>{{user.name}}</span> <input ng-model="user.name"></input> </div></div>
現(xiàn)在既然user是一個(gè)對象了,原型鏈會(huì)被考慮進(jìn)去,navCtrl的模板和$scope也會(huì)隨著loginCtrl更新。
這可能看上去像一個(gè)設(shè)計(jì)好的例子,但當(dāng)涉及到像ngRepeat那樣會(huì)創(chuàng)建子$scope的時(shí)候問題就會(huì)出現(xiàn)。
雖然測試驅(qū)動(dòng)開發(fā)可能不是每一個(gè)開發(fā)者都喜歡的開發(fā)方式,不過每次開發(fā)者去檢查他們的代碼是否工作或開始砸東西時(shí),他們正在做手工測試。
沒有理由不去測試一個(gè)AngularJS應(yīng)用。AngularJS從一開始就是被設(shè)計(jì)地易于測試的。依賴注入和ngMock模塊就是證據(jù)。核心團(tuán)隊(duì)開發(fā)了一些工具來講測試帶到另一個(gè)級(jí)別。
單元測試是一組測試集的基本元素,但隨著應(yīng)用復(fù)雜性的提高,集成測試會(huì)引出更多實(shí)際問題。幸運(yùn)地是AngularJS核心團(tuán)隊(duì)提供了必要的工具。
“我們構(gòu)建了Protractor,一個(gè)端對端的測試運(yùn)行工具,模擬用戶交互,幫助你驗(yàn)證你的Angular應(yīng)用的運(yùn)行狀況?!?/p>
Protractor使用Jasmine測試框架來定義測試。Protractor為不同的頁面交互提供一套健壯的API。
有其他的端對端工具,不過Protractor有著自己的優(yōu)勢,它知道怎么和AngularJS的代碼一起運(yùn)行,特別是面臨$digest循環(huán)的時(shí)候。
一旦使用Protractor寫好了集成測試,測試需要被運(yùn)行起來。等待測試運(yùn)行特別是集成測試,會(huì)讓開發(fā)者感到沮喪。AngularJS核心團(tuán)隊(duì)也感到了這個(gè)痛苦并開發(fā)了Karma。
Karma是一個(gè)Javascript測試運(yùn)行工具,可以幫助你關(guān)閉反饋循環(huán)。Karma可以在特定的文件被修改時(shí)運(yùn)行測試,它也可以在不同的瀏覽器上并行測試。不同的設(shè)備可以指向Karma服務(wù)器來覆蓋實(shí)際場景。
jQuery 是個(gè)很不錯(cuò)的類庫. 它將跨平臺(tái)開發(fā)標(biāo)準(zhǔn)化. 在現(xiàn)代網(wǎng)頁開發(fā)中具有很重要的地位. 雖然 jQuery 擁有許多強(qiáng)大的功能. 但是他的設(shè)計(jì)理念卻與 AngularJS 大相徑庭.
AngularJS 是用來開發(fā)應(yīng)用框架的; jQuery 則是一個(gè)用來簡化 HTML 文檔對象遍歷和操作, 事件處理, 動(dòng)畫以及 Ajax 使用的類庫而已. 這是它們倆在本質(zhì)上的區(qū)別. AngularJS 側(cè)重點(diǎn)在于應(yīng)用的架構(gòu), 而非僅僅是補(bǔ)充 HTML 網(wǎng)頁的功能.
如文檔所述 AngularJS 可以讓你根據(jù)應(yīng)用的需要對 HTML 進(jìn)一步擴(kuò)展. 所以, 如果想要深入的了解 AngularJS 應(yīng)用開發(fā), 就不應(yīng)該再繼續(xù)抱著 jQuery 的大腿. jQuery 只會(huì)把程序員的思維方式限制在現(xiàn)有的 HTML 標(biāo)準(zhǔn)里頭.
DOM操作應(yīng)該出現(xiàn)在指令中,但這并不意味著一定要使用jQuery包裝集。在使用jQuery前要考慮到一些功能AngularJS已經(jīng)提供了。指令建立于相互之間,并可以創(chuàng)建有用的工具。
總有一天,使用jQuery庫是必要的,不過從一開始就引入它無疑是一個(gè)錯(cuò)誤。
感謝你能夠認(rèn)真閱讀完這篇文章,希望小編分享的“AngularJS開發(fā)者常犯的錯(cuò)誤有哪些”這篇文章對大家有幫助,同時(shí)也希望大家多多支持億速云,關(guān)注億速云行業(yè)資訊頻道,更多相關(guān)知識(shí)等著你來學(xué)習(xí)!
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。