介绍AngularJS是当今最流行的JS框架之一,简化开发过程是它的目标之一,这使得它非常适合开发具有小型元类型的应用程序,但也扩展到具有完整功能的客户端应用程序的开发。易于开发、更多的功能和更好的效果会导致更??多的应用程序,但随之而来的是一些陷阱。本文列出了AngularJS的一些常见陷阱和陷阱,尤其是在开发应用程序时。1.MVC目录结构AngularJS是一个缺少更好术语的MVC框架。它的模型没有像backbone.js那样被定义为一个框架,但是它的结构模式还是很匹配的。在MVC框架中工作时,根据文件类型将文件分组在一起是一个常见的要求:.jsfilters/CapatalizeFilter.js看起来是一个合理的布局,特别是对于那些有Rails背景的人。但是当app越来越大的时候,这种布局结构就会导致每次打开一堆文件夹。无论您使用Sublime、VisualStudio还是VimwithNerdTree,每次都需要花费大量时间滚动目录树来查找文件。如果我们根据每个文件的下属功能模块来查找文件,而不是根据它所属的层来查找文件:app/app.jsFeed/_feed.htmlFeedController.jsFeedEntryDirective.jsFeedService.jsLogin/_login.htmlLoginController.jsLoginService.jsShared/CapatalizeFilter.js,找到某个功能模块的文件就容易多了,自然可以提高开发速度。可能不是每个人都同意将html文件和js文件混合在一起的做法。但至少它节省了宝贵的2.通常的做法是在模块分组开始时显示主模块中的所有子模块。但是小应用起步还好,做大了就不好管理了。varapp=angular.module('app',[]);app.service('MyService',function(){//servicecode});app.controller('MyCtrl',function($scope,MyService){//控制器代码});更好的方法是将相似类型的子模块分组:varservices=angular.module('services',[]);services.service('MyService',function(){//servicecode});varcontrollers=angular.module('controllers',['services']);controllers.controller('MyCtrl',function($scope,MyService){//controllercode});varapp=angular.module('app',['控制器','服务']);这个方法和上面的方法类似,但是不是很大。应用要分组的想法将使工作更容易。varsharedServicesModule=angular.module('sharedServices',[]);sharedServices.service('NetworkService',function($http){});varloginModule=angular.module('登录',['sharedServices']);登录模块。服务('登录服务',功能(网络服务){});loginModule.controller('loginCtrl',function($scope,loginService){});varapp=angular.module('app',['sharedServices','login']);在创建大型应用程序时,可能不会将所有模块都放在一个页面上,但按类型对模块进行分组将使模块的可重用性更高。3依赖注入依赖注入是AngularJS最好的模式之一。它使测试更容易,并且还清楚它依赖于哪些对象。AngularJS的注入非常灵活。最简单的方法之一是将依赖项的名称传递给模块的函数:varapp=angular.module('app',[]);app.controller('MainCtrl',function($scope,$timeout){$timeout(function(){console.log($scope);},1000);});在这里,很明显MainCtrl依赖于$scope和$timeout。直到您准备好投入生产并压缩您的代码。使用UglifyJS,上面的例子会变成:varapp=angular.module("app",[]);app.controller("MainCtrl",function(e,t){t(function(){console.log(e)},1e3)})现在AngularJS如何知道MainCtrl依赖什么?AngularJS提供了一个非常简单的解决方案:将依赖项作为字符串数组传递,数组的最后一个元素是一个将所有依赖项作为参数的函数。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)}])3.1全局依赖通常在编写AngularJS应用程序时,会有一个绑定到全局作用域的对象作为依赖。这意味着它在任何AngularJS代码中都可用,但它破坏了依赖注入模型并产生了问题,尤其是在测试中。AngularJS将这些全局变量包装到模块中,因此它们可以像标准AngularJS模块一样被注入。Underscore.js是一个很棒的库,可以将Javascript代码简化为功能模式,并且可以将其转换为模块:varunderscore=angular.module('underscore',[]);underscore.factory('_',function(){returnwindow._;//Underscore必须已经加载到页面上});varapp=angular.module('app',['underscore']);app.controller('MainCtrl',['$scope','_',function($scope,_){init=function(){_.keys($scope);}init();}]);它允许应用程序继续使用AngularJS依赖注入样式,也允许在测试期间交换下划线出来。这可能看起来不重要,就像一件微不足道的工作,但如果您的代码使用usestrict(并且应该如此),那么它就变得很有必要了。4ControllerBloat控制器是AngularJS应用程序的核心。在控制器中放入过多逻辑很容易,尤其是一开始。控制器不应该做任何DOM操作或有DOM选择器,这应该通过使用ngModel指令来完成。同样,业务逻辑应该在服务中,而不是控制器中。数据也应该存储在服务中,除非它已经与$scope相关联。服务是在整个应用程序生命周期中持续存在的实体,而控制器在应用程序阶段之间是短暂的。如果数据存储在controller中,重新实例化时需要从别处取数据。即使数据存储在localStorage中,获取数据也比从Javascript变量中获取数据慢几个数量级。AngularJS在遵循简单责任原则(SRP)时效果最好。如果控制器是视图和模型的协调器,那么它所具有的逻辑应该被最小化。这将使测试更容易。5Service和Factory的区别几乎每个刚接触AngularJS的开发者都会对这两个东西感到困惑。虽然它们达到(几乎)相同的效果,但它实际上不是语法糖。以下是它们在AngularJS源代码中的定义:注射器){返回$injector.instantiate(构造函数);}]);}从源码可以看出,服务函数只是调用了工厂函数,然后工厂函数调用了提供者函数。其实value、constant、decorator也是AngularJS提供的provider的封装,只是它们的使用场景没有那么混乱,文档描述的也很清楚。那么Service是不是简单的调用一次工厂函数呢?重点是$injector.instantiate;在此函数中,服务将接收由$injector使用new关键字实例化的构造函数对象。(原文:withinthisfunction$injectorcreatesanewinstanceoftheservice'sconstructorfunction.)下面是完成相同功能的服务和工厂。varapp=angular.module('app',[]);app.service('helloWorldService',function(){this.hello=function(){return"HelloWorld";};});app.factory('helloWorldFactory',function(){return{hello:function(){return"HelloWorld";}}});当helloWorldService或helloWorldFactory中的任何一个被注入到控制器中时,它们都有一个名称,该名称为hello方法返回字符串“HelloWorld”。这个服务的构造函数在声明的时候只实例化了一次,每次注入工厂对象的时候,都会相互进行各种引用,但是这个工厂还是只实例化了一次。所有提供者都是单身人士。既然两者执行相同的功能,为什么会存在这两种格式呢?工厂提供的灵活性比服务稍微大一点,因为它们可以返回函数,然后可以使用new关键字对其进行新建。在其他地方,来自面向对象编程的工厂模式。工厂可以是用于创建其他对象的对象。app.factory('helloFactory',function(){returnfunction(name){this.name=name;this.hello=function(){return"Hello"+this.name;};};});这里是一个使用了上面提到的服务和两个工厂控制器的例子。需要注意的是,helloFactory返回的是一个函数,变量名的值是在对象使用new关键字的时候设置的。app.controller('helloCtrl',function($scope,helloWorldService,helloWorldFactory,helloFactory){init=function(){helloWorldService.hello();//'HelloWorld'helloWorldFactory.hello();//'HelloWorld'newhelloFactory('Readers').hello()//'HelloReaders'}init();});一开始只用services.Factory更适合设计需要私有方法的类时使用:app.factory('privateFactory',function(){varprivateFunc=function(name){returnname.split("").reverse().join("");//reversesthename};return{hello:function(name){return"Hello"+privateFunc(name);}};});在此示例中,privateFactory包含一个无法从外部访问的私有privateFunc函数。这种使用服务的方式也可以实现,但是使用Factory代码结构更加清晰。6不会用BatarangBatarang是一个优秀的chrome浏览器插件,用于开发和调试AngularJS应用。Batarang提供了一个模型浏览器来查看Angular中哪些模型已绑定到范围。可用于在运行时查看绑定到指令中隔离范围的值。Batarang还提供了一个依赖图。对于引入未经测试的代码库,该工具可以快速确定哪些服务应该受到更多关注。***,Batarang提供分析。AngularJS虽然是开箱即用的高性能,但是随着应用自定义指令和复杂业务逻辑的增长,有时感觉页面不够流畅。使用Batarang的性能分析工具可以很容易地看到哪些功能在摘要周期中占用了更多的时间。这个工具还可以显示整个监控树(fullwatchtree),当页面的监控器(watch)过多时很有用。7太多观察者如上所述,拥有外部AngularJS是件好事。因为在一个循环消化中需要进行脏检查,一旦watcher的数量超过2000个,这个循环就会出现明显的问题。(2,000只是一个参考数字,在1.3版本中,AngularJS对循环消化有更严格的控制。AaronGraye对此有更详细的描述。)这个IIFE(快速响应函数)可以输出当前页面的观察者数量,只需将其复制到控制台即可查看详细信息。IIFE的来源类似于Jared在StackOverflow上的回答。(function(){varroot=$(document.getElementsByTagName('body'));varwatchers=[];varf=function(element){if(element.data().hasOwnProperty('$scope')){角度。forEach(element.data().$scope.$$watchers,function(watcher){watchers.push(watcher);});}angular.forEach(element.children(),function(childElement){f($(childElement));});};f(root);console.log(watchers.length);})();使用它,您可以从Batarang的效率中确定watchers和watchtrees的数量,正如您在Thereisawatchforthedatawherethereiswatchorwherethereareorthereisnochange.当有数据没有变化,但你希望它成为Angular中的模板时,你可以考虑使用bindonce。Bindonce只是Angular中可能使用模板的命令,但不会增加watch的数量。8检查$scopeJavascript的基于原型的继承与基于类的继承在某些细微的方面有所不同。通常这不是问题,但在使用$scope时经常会出现差异。在AngularJS中,每个$scope都继承自其父$scope,最高层是$rootScope。($scope在指令中的行为有些不同,其中独立的作用域仅继承那些显式声明的属性。)从父级共享数据对于原型继承并不重要。但是,如果您不小心,它可能会遮蔽父级$scope的属性。我们想在导航栏上呈现用户名,然后转到登录表单。
