(转载)AngularJS开发人员最常犯的10个错误
2016-09-17 10:09
441 查看
简介
AngularJS是目前最为活跃的Javascript框架之一,AngularJS的目标之一是简化开发过程,这使得AngularJS非常善于构建小型app原型,但AngularJS对于全功能的客户端应用程序同样强大,它结合了开发简便,特性广泛和出众的性能,使其被广泛使用。然而,大量使用也会产生诸多误区。以下这份列表摘取了常见的一些AngularJS的错误用法,尤其是在app开发过程中。
1. MVC目录结构
AngularJS,直白地说,就是一个MVC框架。它的模型并没有像backbone.js框架那样定义的如此明确,但它的体系结构却恰如其分。当你工作于一个MVC框架时,普遍的做法是根据文件类型对其进行归类:JavaScript
123456789101112131415 | templates/ _login.html _feed.htmlapp/ app.js controllers/ LoginController.js FeedController.js directives/ FeedEntryDirective.js services/ LoginService.js FeedService.js filters/ CapatalizeFilter.js |
1 2 3 4 5 6 7 8 9 10 11 12 13 | app/ app.js Feed/ _feed.html FeedController.js FeedEntryDirective.js FeedService.js Login/ _login.html LoginController.js LoginService.js Shared/ CapatalizeFilter.js |
2. 模块
将所有东西都一股脑放在主模块下是很常见的,对于小型app,刚开始并没有什么问题,然而很快你就会发现坑爹的事来了。JavaScript
1234567 | var app = angular.module('app',[]);app.service('MyService', function(){ //service code});app.controller('MyCtrl', function($scope, MyService){ //controller code}); |
1 2 3 4 5 6 7 8 9 10 11 | 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']); |
JavaScript
12345678 | 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']); |
3. 依赖注入
依赖注入是AngularJS最好的模式之一,它使得测试更为简单,并且依赖任何指定对象都很明确。AngularJS的注入方式非常灵活,最简单的方式只需要将依赖的名字传入模块的function中即可:JavaScript1 2 3 4 5 6 7 | var app = angular.module('app',[]); app.controller('MainCtrl', function($scope, $timeout){ $timeout(function(){ console.log($scope); }, 1000); }); |
直到你准备将其部署到生产环境并希望精简代码时,一切都很美好。如果使用UglifyJS,之前的例子会变成下面这样:
JavaScript
12 | var app=angular.module("app",[]);app.controller("MainCtrl",function(e,t){t(function(){console.log(e)},1e3)}) |
1 2 3 4 5 | app.controller('MainCtrl', ['$scope', '$timeout', function($scope, $timeout){ $timeout(function(){ console.log($scope); }, 1000); }]); |
JavaScript
1 | app.controller("MainCtrl",["$scope","$timeout",function(e,t){t(function(){console.log(e)},1e3)}]) |
3.1 全局依赖
在编写AngularJS程序时,时常会出现这种情况:某个对象有一个依赖,而这个对象又将其自身绑定在全局scope上,这意味着在任何AngularJS代码中这个依赖都是可用的,但这却破坏了依赖注入模型,并会导致一些问题,尤其体现在测试过程中。使用AngularJS可以很容易的将这些全局依赖封装进模块中,所以它们可以像AngularJS标准模块那样被注入进去。Underscrore.js是一个很赞的库,它可以以函数式的风格简化Javascript代码,通过以下方式,你可以将其转化为一个模块:JavaScript1 2 3 4 5 6 7 8 9 10 11 12 13 | 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(); }]); |
这可能看上去十分琐碎,没什么必要,但如果你的代码中正在使用use strict(而且必须使用),那这就是必要的了。
4. 控制器膨胀
控制器是AngularJS的肉和土豆,一不小心就会将过多的逻辑加入其中,尤其是刚开始的时候。控制器永远都不应该去操作DOM,或是持有DOM选择器,那是我们需要使用指令和ng-model的地方。同样的,业务逻辑应该存在于服务中,而非控制器。数据也应该存储在服务中,除非它们已经被绑定在$scope上了。服务本身是单例的,在应用程序的整个生命周期都存在,然而控制器在应用程序的各状态间是瞬态的。如果数据被保存在控制器中,当它被再次实例化时就需要重新从某处获取数据。即使将数据存储于localStorage中,检索的速度也要比Javascript变量慢一个数量级。
AngularJS在遵循单一职责原则(SRP)时运行良好,如果控制器是视图和模型间的协调者,那么它所包含的逻辑就应该尽量少,这同样会给测试带来便利。
5. Service vs Factory
几乎每一个AngularJS开发人员在初学时都会被这些名词所困扰,这真的不太应该,因为它们就是针对几乎相同事物的语法糖而已!以下是它们在AngularJS源代码中的定义:
JavaScript
123456789 | function factory(name, factoryFn) { return provider(name, { $get: factoryFn }); } function service(name, constructor) { return factory(name, ['$injector', function($injector) { return $injector.instantiate(constructor); }]);} |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | 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"; } } }); |
既然能做相同的事,为什么需要两种不同的风格呢?相对于service,factory提供了更多的灵活性,因为它可以返回函数,这些函数之后可以被新建出来。这迎合了面向对象编程中工厂模式的概念,工厂可以是一个能够创建其他对象的对象。
JavaScript
123456789 | app.factory('helloFactory', function() { return function(name) { this.name = name; this.hello = function() { return "Hello " + this.name; }; };}); |
1 2 3 4 5 6 7 8 9 | 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(); }); |
Factory在设计一个包含很多私有方法的类时也很有用:
JavaScript
1234567891011 | app.factory('privateFactory', function(){ var privateFunc = function(name) { return name.split("").reverse().join(""); //reverses the name }; return { hello: function(name){ return "Hello " + privateFunc(name); } };}); |
6. 没有使用Batarang
Batarang是一个出色的Chrome插件,用来开发和测试AngularJS app。Batarang提供了浏览模型的能力,这使得我们有能力观察AngularJS内部是如何确定绑定到作用域上的模型的,这在处理指令以及隔离一定范围观察绑定值时非常有用。Batarang也提供了一个依赖图, 如果我们正在接触一个未经测试的代码库,这个依赖图就很有用,它能决定哪些服务应该被重点关照。最后,Batarang提供了性能分析。Angular能做到开包即用,性能良好,然而对于一个充满了自定义指令和复杂逻辑的应用而言,有时候就不那么流畅了。使用Batarang性能工具,能够直接观察到在一个digest周期中哪个函数运行了最长时间。性能工具也能展示一棵完整的watch树,在我们拥有很多watcher时,这很有用。7. 过多的watcher
在上一点中我们提到,AngularJS能做到开包即用,性能良好。由于需要在一个digest周期中完成脏数据检查,一旦watcher的数量增长到大约2000时,这个周期就会产生显著的性能问题。(2000这个数字不能说一定会造成性能大幅下降,但这是一个不错的经验数值。在AngularJS 1.3 release版本中,已经有一些允许严格控制digest周期的改动了,Aaron Gray有一篇很好的文章对此进行解释。)以下这个“立即执行的函数表达式(IIFE)”会打印出当前页面上所有的watcher的个数,你可以简单的将其粘贴到控制台中,观察结果。这段IIFE来源于Jared在StackOverflow上的回答:JavaScript1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | (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); })(); |
当存在不变数据,而你又想用AngularJS将其模版化,可以考虑使用bindonce。Bindonce是一个简单的指令,允许你使用AngularJS中的模版,但它并不会加入watch,这就保证了watch数量不会增长。
8. 限定$scope的范围
Javascript基于原型的继承与面向对象中基于类的继承有着微妙的区别,这通常不是什么问题,但这个微妙之处在使用$scope时就会表现出来。在AngularJS中,每个$scope都会继承父$scope,最高层称之为$rootScope。($scope与传统指令有些不同,它们有一定的作用范围i,且只继承显式声明的属性。)由于原型继承的特点,在父类和子类间共享数据不太重要,不过如果不小心的话,也很容易误用了一个父$scope的属性。
比如说,我们需要在一个导航栏上显示一个用户名,这个用户名是在登录表单中输入的,下面这种尝试应该是能工作的:
XHTML
1234567 | <div ng-controller="navCtrl"> <span>{{user}}</span> <div ng-controller="loginCtrl"> <span>{{user}}</span> <input ng-model="user"></input> </div></div> |
1 2 3 4 5 6 7 | <div ng-controller="navCtrl"> <span>{{user.name}}</span> <div ng-controller="loginCtrl"> <span>{{user.name}}</span> <input ng-model="user.name"></input> </div> </div> |
这看上去是一个很做作的例子,但是当你使用某些指令去创建子$scope,如ngRepeat时,这个问题很容易就会产生。
转自:http://blog.jobbole.com/78946/
相关文章推荐
- AngularJS开发人员最常犯的10个错误
- AngularJS开发人员最常犯的10个错误
- AngularJS开发人员最常犯的10个错误
- AngularJS开发人员最常犯的10个错误
- AngularJS开发最常犯的10个错误
- Java开发人员最常犯的10个错误
- Web 开发人员应当收藏的10个网站(转载)
- Web开发人员常犯的10个错误
- AngularJS 开发中常犯的10个错误
- Java(Android)开发人员最常犯的10个错误
- 【转载】对Web开发人员极有帮助的10个流程图
- JAVA开发人员在编写SQL时最容易犯的10个错误
- Java开发人员最常犯的10个错误
- Web开发人员常犯的10个错误
- Java开发人员最常犯的10个错误
- Java开发人员最常犯的10个错误
- IE调试网页之四:F12 开发人员工具控制台错误消息 (Windows)(转载)
- C++编程人员容易犯的10个C#错误
- NET开发人员必备的10个Visual Studio Add-Ins
- C++编程人员容易犯的10个C#错误