我很久之前便听说了angularJS的大名,之前的leader也经常感叹angularJS的设计如何如何精妙,可叹一直没有机会深入了解,国庆长假因为没钱出游,倒是可以对他做一个了解......

根据之前的经验,就现有的前端项目,如果最初没有良好的设计,做到一定阶段一定会变得难以维护,就算最初有设计,变化无常的PM也会让你的项目BUG丛生。

一个页面的复杂程度不断的增加,依赖模块也会变得混乱,而其中最为头疼的就是页面级随心所欲的DOM操作了!

MVC类的框架可以很好的解决以上问题,而号称MVVM的angularJS在处理这种情况似乎更有话语权,所以我们今天便来好好研究其一番。

angular适合做具有复杂数据交互的前端应用,他旨在让我们摆脱繁琐的DOM操作,而将注意力集中在业务逻辑上,这里摆脱繁琐的DOM操作是个非常关键的愿景,也是很多人不太理解,甚至会将jQuery这种库与Backbone或者angularJS这种框架做对比的原因。

jQuery是非常优秀的DOM操作工具库,在DOM操作上,基本没有库能超越他了
但Backbone&angularJS这种MVC是框架提供的是完整的解决方案,甚至会依赖jQuery&zepto,他们是两个东西,不能互相比较,所以完全没有angularJS要取代jQuery的可能,而当DOM操作过于杂乱一定是你的项目出了问题。

这里举个jQuery不依赖MVC骨架的例子,我们的订单填写页,需要在商品数量变化后导致金额变化,并且没有选商品时,支付按钮不可点击:

摆脱DOM操作,从TodoMVC看angularJS

对于一个有些经验的菜鸟来说,可能会这样写代码:

$('#reduceNum').click(function() {
      $('#payBar #num').text($('#curNum').html() - 1);
});

对于一些有一定经验的老鸟来说,可能会这样写代码:

1 events: {
2   'click #reduceNum': reduceNumAction  
3 },
4 
5 reduceNumAction: function() {
6   $('#payBar #num').text($('#curNum').html() - 1);
7 }

第一段代码可能会导致你年底加薪无望,并且在团队中没有话语权;而第二段代码积累到一定量后会让这个项目变得不可维护:

① 支付工具栏初始化状态如何显示,如果数字组件按需做异步加载,这个显示将变得更加负责。

② 哪些操作将导致支付栏变化,你如何组织这些变化的代码,是让他四散到各处,还是集合在一起,集合后导致函数过大怎么办?

③ 新增的导致工具栏变化的操作会不会对原来的操作造成影响,新增的代码放在何处?

④ 如果有地方要使用工具栏处的信息,取的信息会不会是无效的(取的时候可能正在变化),应该通过DOM取还是内存取?

⑤ 如果支付栏DOM结构如果变化,对你的程序影响有多大,如何主流程的影响,比较支付点击后只需要操作数据,不需要关注DOM?

⑥ ......

这个就是仅仅依赖jQuery要面临的问题,并且这种问题是无解的,因为这里的专注点是DOM操作而不是数据,如果将关注点变成了数据,代码就不是这样写的,DOM操作仅仅是过程而不是目的,我们代码的目的,往往是展示数据、获取数据,这点一定要清晰。

所以让我们带着这些问题:angular的优势在何处,他如何改善我们的编程体验,进入今天的学习吧。

初探angularJS

Hello World

学习任何一门语言,Hello world是必不可少的,他是我们迈向精通的唯一路径:

1 <!doctype html>
2 <html ng-app>
3 <head>
4   <script src="angular.js" type="text/javascript"></script>
5 </head>
6 <body>
7   Hello {{'World'}}!
8 </body>
9 </html>

被{{}}包裹的便是angularJS变量,上述程序稍作改变的话:

 1 <!doctype html>
 2 <html ng-app>
 3 <head>
 4   <script src="angular.js" type="text/javascript"></script>
 5 </head>
 6 <body>
 7   <input ng-model="name" type="text" />
 8   Hello {{name}}!
 9 </body>
10 </html>

便会同步显示文本框输入内容,这里通信的基础是model对应着ng-model,只要被ng-app包裹就会受angularJS控制,用angularJS自己的话说:HTML标签增强

作用域

为什么文本框中的变化会体现在外层,这个涉及到了ng-model的双向绑定知识,我们暂时不予理睬,但是外层又是从哪里读取name这个变量的呢?

在angular中,属性会存储在一个@scope(作用域)的对象上,每次我们对文本框的更新皆会通知$scope上的name属性,在angular中,$scope是连接controllers(控制器)与template(视图)的主要胶合器。

上述代码完全不涉及js代码,真实的场景中每个代码段会对controller做依赖,我们这里对代码做一些更改:

 1 <!doctype html>
 2 <html>
 3 <head>
 4   <script src="angular.js" type="text/javascript"></script>
 5 </head>
 6 <body ng-app="app" ng-controller="MainCtrl">
 7   <h1 ng-click="click()">
 8     Hello {{name}}!
 9   </h1>
10   <script>
11     var app = angular.module('app', []);
12     app.controller('MainCtrl', function ($scope) {
13       $scope.name = 'World';
14       $scope.click = function () {
15         $scope.name = '霹雳布袋戏';
16       };
17     });
18   </script>
19 </body>
20 </html>

这里首先定义了一个application模块,后续会看见,我们每次代码一定会新建一个application,相当于命名空间的意思,后面还可以做依赖用。

接着,我们创建了一个controller模块,这里已经有点MVC的味道了,controller接受$scope属性,这个时候模板上所有子标签对这个控制器中的属性便有了访问权限,这里用到了一些angular指令

ng-app:告诉html标签已经处于angular的控制了,可以使用angular的特性
ng-controller:一个module下面可以包括多个控制器,每一个标签所属的控制器由该指令指定

上述代码是将控制器中的数据读出来,我们同样也可以将View中的数据读入到控制器:

 1 <!doctype html>
 2 <html>
 3 <head>
 4   <script src="angular.js" type="text/javascript"></script>
 5 </head>
 6 <body ng-app="app" ng-controller="MainCtrl">
 7   <input type="text" ng-model="message" />
 8   <h1 ng-click="click()">
 9     Hello {{name}}!
10   </h1>
11   <script>
12     var app = angular.module('app', []);
13     app.controller('MainCtrl', function ($scope) {
14       $scope.name = 'World';
15       $scope.click = function () {
16         $scope.name = $scope.message;
17       };
18     });
19   </script>
20 </body>
21 </html>

PS:看到这里,老夫虎躯为之一振,对该特性的实现产生了兴趣,后续值得深入

指令

指令让我们有能力使用angular规定的方式为HTML标签增加新特性,angular内置了很多有用的指令,这里仍然举一个简单的例子说明问题:

 1 <!doctype html>
 2 <html>
 3 <head>
 4   <script src="angular.js" type="text/javascript"></script>
 5 </head>
 6 <body ng-app="app">
 7   <ul ng-controller="MainCtrl">
 8     <li ng-repeat="v in arr">{{v}}</li>
 9   </ul>
10   <script>
11     var app = angular.module('app', []);
12     app.controller('MainCtrl', function ($scope) {
13       $scope.arr = ['素还真', '一页书', '叶小钗']
14     });
15   </script>
16 </body>
17 </html>

我们除了使用angular的内置指令外,还可以自定义指令,比如这里的让文本框自动获取焦点的指令:

 1 <!doctype html>
 2 <html>
 3 <head>
 4   <script src="angular.js" type="text/javascript"></script>
 5 </head>
 6 <body ng-app="app" ng-controller="MainCtrl">
 7   <input type="text" focus  ng-model="user.name" />
 8   <button ng-click="greet()">
 9     Click here!</button>
10   <h3>
11     {{ message }}</h3>
12   <script>
13     var app = angular.module('app', []);
14     app.controller('MainCtrl', function ($scope) {
15       $scope.greet = function () {
16         $scope.message = "Hello, " + $scope.user.name;
17       }
18     });
19     app.directive('focus', function () {
20       return {
21         link: function (scope, element, attrs) {
22           element[0].focus();
23         }
24       };
25     });
26   </script>
27 </body>
28 </html>

指令的使用可以很复杂,后续我们会更加深入,这里再举一个单独使用的例子:

 1 <!doctype html>
 2 <html>
 3 <head>
 4   <script src="angular.js" type="text/javascript"></script>
 5 </head>
 6 <body ng-app="app">
 7   <hello></hello>
 8   <script>
 9     var app = angular.module('app', []);
10     app.directive('hello', function () {
11       return {
12         restrict: "E",
13         replace: true,
14         template: "<div>显示固定数据,类似自定义标签</div>"
15       }
16     });
17   </script>
18 </body>
19 </html>

指令的定义有很多参数,可以指定该指令作为属性还是作为标签,这个我们后续再深入了解。

过滤器

感觉过滤器是参考的smarty的语法,一般而言是用作显示的增强,angular本身也提供了很多内置过滤器,比如:

1 {{ "aaaa" | uppercase }} // AAAA
2 {{ "BBBB" | lowercase }} // bbbb

感觉比较有用的是日期操作过滤器:

{{ 1427345339072 | date:'yyyy' }} // 2015
{{ 1427345339072 |date:'MM' }} // 03
{{ 1427345339072 | date:'d' }} // 26,一月中第多少天
......

数字格式化:

{{12.13534|number:2}} // 12.14 四舍五入保留两位小数
{{10000000|number}} // 10,000,000

当然,我们可以使用自定义过滤器,比如这里我想对超出某一区间的数字加...

 1 <!doctype html>
 2 <html>
 3 <head>
 4   <script src="angular.js" type="text/javascript"></script>
 5 </head>
 6 <body ng-app="app" ng-controller="MainCtrl">
 7   <input type="text" ng-model="message" />
 8   <h3>
 9     {{ message |myFilter }}</h3>
10   <script>
11     var app = angular.module('app', []);
12     app.controller('MainCtrl', function ($scope) {
13       $scope.message = '';
14     });
15 
16     app.filter('myFilter', function () {
17       return function (input, param) {
18         return input.length < 5 ? input : input.substring(0, 5) + '...'
19       }
20     });
21   </script>
22 </body>
23 </html>

具备了以上知识,我们尝试进入To都MVC看看

参考:http://www.cnblogs.com/whitewolf/p/angularjs-start.html

TodoMVC

我们由最新的TodoMVC下载代码:http://todomvc.com/,首先查看js引用情况:

1 <script src="node_modules/angular/angular.js"></script>
2 <script src="node_modules/angular-route/angular-route.js"></script>
3 <script src="js/app.js"></script>
4 <script src="js/controllers/todoCtrl.js"></script>
5 <script src="js/services/todoStorage.js"></script>
6 <script src="js/directives/todoFocus.js"></script>
7 <script src="js/directives/todoEscape.js"></script>

除了angular本体文件外,还多了个angular的扩展,做单页应用的路由功能的,这个路由代码量不大,使用和Backbone的路由比较类似;app.js为入口文件,配置路由的地方;余下是控制器文件文件以及一个localstorage的操作服务,余下就是指令了。

代码首先定义了一个模块作为本次程序的命名空间:

1 angular.module('todomvc', ['ngRoute'])

ngRoute为其依赖项,可以从route的定义看出:

1 var ngRouteModule = angular.module('ngRoute', ['ng']).
2                         provider('$route', $RouteProvider),
3     $routeMinErr = angular.$$minErr('ngRoute');

这里来看看其router的配置,以及index.html的写法:

 1 <!doctype html>
 2 <html lang="en" data-framework="angularjs">
 3     <head>
 4         <meta charset="utf-8">
 5         <title>AngularJS • TodoMVC</title>
 6         <link rel="stylesheet" href="node_modules/todomvc-common/base.css">
 7         <link rel="stylesheet" href="node_modules/todomvc-app-css/index.css">
 8         <style>[ng-cloak] { display: none; }</style>
 9     </head>
10     <body ng-app="todomvc">
11         <ng-view />
12 
13         <script type="text/ng-template" id="todomvc-index.html">
14             <section id="todoapp">
15                 <header id="header">
16                     <h1>todos</h1>
17                     <form id="todo-form" ng-submit="addTodo()">
18                         <input id="new-todo" placeholder="What needs to be done?" ng-model="newTodo" ng-disabled="saving" autofocus>
19                     </form>
20                 </header>
21                 <section id="main" ng-show="todos.length" ng-cloak>
22                     <input id="toggle-all" type="checkbox" ng-model="allChecked" ng-click="markAll(allChecked)">
23                     <label for="toggle-all">Mark all as complete</label>
24                     <ul id="todo-list">
25                         <li ng-repeat="todo in todos | filter:statusFilter track by $index" ng-class="{completed: todo.completed, editing: todo == editedTodo}">
26                             <div class="view">
27                                 <input class="toggle" type="checkbox" ng-model="todo.completed" ng-change="toggleCompleted(todo)">
28                                 <label ng-dblclick="editTodo(todo)">{{todo.title}}</label>
29                                 <button class="destroy" ng-click="removeTodo(todo)"></button>
30                             </div>
31                             <form ng-submit="saveEdits(todo, 'submit')">
32                                 <input class="edit" ng-trim="false" ng-model="todo.title" todo-escape="revertEdits(todo)" ng-blur="saveEdits(todo, 'blur')" todo-focus="todo == editedTodo">
33                             </form>
34                         </li>
35                     </ul>
36                 </section>
37                 <footer id="footer" ng-show="todos.length" ng-cloak>
38                     <span id="todo-count"><strong>{{remainingCount}}</strong>
39                         <ng-pluralize count="remainingCount" when="{ one: 'item left', other: 'items left' }"></ng-pluralize>
40                     </span>
41                     <ul id="filters">
42                         <li>
43                             <a ng-class="{selected: status == ''} " href="#/">All</a>
44                         </li>
45                         <li>
46                             <a ng-class="{selected: status == 'active'}" href="#/active">Active</a>
47                         </li>
48                         <li>
49                             <a ng-class="{selected: status == 'completed'}" href="#/completed">Completed</a>
50                         </li>
51                     </ul>
52                     <button id="clear-completed" ng-click="clearCompletedTodos()" ng-show="completedCount">Clear completed</button>
53                 </footer>
54             </section>
55             <footer id="info">
56                 <p>Double-click to edit a todo</p>
57                 <p>Credits:
58                     <a href="http://twitter.com/cburgdorf">Christoph Burgdorf</a>,
59                     <a href="http://ericbidelman.com">Eric Bidelman</a>,
60                     <a href="http://jacobmumm.com">Jacob Mumm</a> and
61                     <a href="http://igorminar.com">Igor Minar</a>
62                 </p>
63                 <p>Part of <a href="http://todomvc.com">TodoMVC</a></p>
64             </footer>
65         </script>
66 <script src="node_modules/angular/angular.js"></script>
67 <script src="node_modules/angular-route/angular-route.js"></script>
68 <script src="js/app.js"></script>
69 <script src="js/controllers/todoCtrl.js"></script>
70 <script src="js/services/todoStorage.js"></script>
71 <script src="js/directives/todoFocus.js"></script>
72 <script src="js/directives/todoEscape.js"></script>
73     </body>
74 </html>
index.html

相关文章: