当前位置: 首页 > 科技观察

AngularJS开发者最常犯的10个错误

时间:2023-03-11 21:56:13 科技观察

介绍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的属性。我们想在导航栏上呈现用户名,然后转到登录表单。{{user}}{{user}}

考你:当用户在设置了ngModel的文本框中输入一个值时,哪个模板会被更新?是navCtrl、loginCtrl还是两者兼而有之?如果你选择了loginCtrl,那么你可能会对原型继承的机制有更好的理解。查找文字时,不会触及原型链。如果要更新navCtrl,则必须找到原型链。当值是对象时会发生这种情况。(记住在Javascript中,函数和数组对象都算作对象)所以要达到想要的效果,需要在navCtrl上创建一个对象,让loginCtrl可以引用。{{user.name}}{{user.name}}
既然user是一个对象,原型链就会被考虑进去,navCtrl的template和$scope也会随着loginCtrl一起更新。这看起来像是一个设计良好的示例,但是当涉及到创建子$scope的ngRepeat之类的东西时,问题就出现了。9.手动测试虽然测试驱动开发可能不是每个开发人员都喜欢的开发风格,但每次开发人员去检查他们的代码是否工作或开始破坏东西时,他们都在进行手动测试。没有理由不测试AngularJS应用程序。AngularJS是从头开始设计的,易于测试。依赖注入和ngMock模块就是证明。核心团队开发了一些工具来将测试提升到另一个层次。9.1Protractor单元测试是测试集的基本要素,但随着应用复杂度的增加,集成测试会导致更多的实际问题。幸运的是AngularJS核心团队提供了必要的工具。“我们构建了量角器,这是一个端到端的测试运行器,可以模拟用户交互,帮助您验证Angular应用程序的健康状况。”Protractor使用Jasmine测试框架来定义测试。Protractor为不同的页面交互提供了强大的API。还有其他端到端工具,但Protractor有其自身的优势,它知道如何与AngularJS代码一起运行,尤其是在面对$digest循环时。9.2Karma一旦使用Protractor编写了集成测试,就需要运行测试。等待测试运行,尤其是集成测试,可能会让开发人员感到沮丧。AngularJS核心团队也感受到了这种痛苦,开发了Karma。Karma是一个Javascript测试运行器,可帮助您关闭反馈循环。Karma可以在修改特定文件时运行测试,并且可以在不同的浏览器上并行运行测试。不同的设备可以指向Karma服务器来覆盖实际场景。10jQuery的使用jQuery是一个非常优秀的类库。它标准化了跨平台开发。它在现代Web开发中扮演着非常重要的角色。尽管jQuery有很多强大的功能,但它的设计理念与AngularJS有很大的不同。AngularJS用于开发应用程序框架;jQuery是一个用于简化HTML文档对象遍历和操作、事件处理、动画和Ajax使用的类库。这是它们之间的本质区别。AngularJS专注于应用程序架构,而不是仅仅补充HTML网页的功能。如文档中所述,AngularJS允许您根据应用程序的需要进一步扩展HTML。所以,想要深入了解AngularJS应用开发,就不要继续抱jQuery的大腿了。jQuery只是将程序员的思维方式限制在现有的HTML标准中。DOM操作应该出现在指令中,但这并不意味着必须使用jQuery包装器。在使用jQuery之前,请考虑AngularJS已经提供的一些功能。说明相互依存,可以创建有用的工具。总有一天,使用jQuery库是必要的,但首先包含它绝对是一个错误。总结AngularJS是一个很棒的框架,并且随着它的社区一起成长。传统的AngularJS仍然是一个发展中的概念,但希望可以避免上述规划AngularJS应用程序的陷阱。