取代jQuery?
我很久之前便聽說了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骨架的例子,我們的訂單填寫頁,需要在商品數量變化後導緻金額變化,并且沒有選商品時,支付按鈕不可點選:
對于一個有些經驗的菜鳥來說,可能會這樣寫代碼:
$('#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上的name屬性,在angular中,scope是連接配接controllers(控制器)與template(視圖)的主要膠合器。
上述代碼完全不涉及js代碼,真實的場景中每個代碼段會對controller做依賴,我們這裡對代碼做一些更改:
2 <html>
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中的資料讀入到控制器:
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内置了很多有用的指令,這裡仍然舉一個簡單的例子說明問題:
6 <body ng-app="app">
7 <ul ng-controller="MainCtrl">
8 <li ng-repeat="v in arr">{{v}}</li>
9 </ul>
13 $scope.arr = ['素還真', '一頁書', '葉小钗']
14 });
15 </script>
16 </body>
17 </html>
我們除了使用angular的内置指令外,還可以自定義指令,比如這裡的讓文本框自動擷取焦點的指令:
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 }
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>
指令的使用可以很複雜,後續我們會更加深入,這裡再舉一個單獨使用的例子:
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
當然,我們可以使用自定義過濾器,比如這裡我想對超出某一區間的數字加...
8 <h3>
9 {{ message |myFilter }}</h3>
13 $scope.message = '';
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的寫法:
index.html
1 var routeConfig = {
2 controller: 'TodoCtrl',
3 templateUrl: 'todomvc-index.html',
4 resolve: {
5 store: function (todoStorage) {
6 // Get the correct module (API or localStorage).
7 return todoStorage.then(function (module) {
8 module.get(); // Fetch the todo records in the background.
9 return module;
10 });
11 }
12 }
13 };
14
15 $routeProvider
16 .when('/', routeConfig)
17 .when('/:status', routeConfig)
18 .otherwise({
19 redirectTo: '/'
這個代碼現在基本看不懂,大概意思應該就是根據路由執行config中的邏輯,将模闆展示在頁面上,其中index.html有一段代碼應該是用于替換模闆的:
<ng-view />
我們先抛開那段看不懂的,直奔主流程,目光聚焦到控制器controller:
View Code
這段代碼130行不到,讓我體會到了深深的神奇,首先我們在app中傳回了讀取到localstorage的對象:
1 resolve: {
2 store: function (todoStorage) {
3 // Get the correct module (API or localStorage).
4 return todoStorage.then(function (module) {
5 module.get(); // Fetch the todo records in the background.
6 return module;
7 });
8 }
9 }
然後就在controller的依賴項中讀到了被注入的對象:
var todos = $scope.todos = store.todos;
此時,模闆也被插到了頁面上,等待controller的執行:
首先這裡有一個$watch方法,監控着todos的變化,每次變化都會展現到這裡,導緻view的變化:
1 $scope.$watch('todos', function () {
2 $scope.remainingCount = $filter('filter')(todos, { completed: false }).length;
3 $scope.completedCount = todos.length - $scope.remainingCount;
4 $scope.allChecked = !$scope.remainingCount;
5 }, true);
然後我們将關注點放在新增項目上:
1 $scope.addTodo = function () {
2 var newTodo = {
3 title: $scope.newTodo.trim(),
4 completed: false
5 };
6
7 if (!newTodo.title) {
8 return;
9 }
10
11 $scope.saving = true;
12 store.insert(newTodo)
13 .then(function success() {
14 $scope.newTodo = '';
15 })
16 .finally(function () {
17 $scope.saving = false;
18 });
19 };
View上的調用點是:
1 <header id="header">
2 <h1>todos</h1>
3 <form id="todo-form" ng-submit="addTodo()">
4 <input id="new-todo" placeholder="What needs to be done?" ng-model="newTodo" ng-disabled="saving" autofocus>
5 </form>
6 </header>
首先這段代碼中有一個autofocus的指令,沒有什麼卵用:
可以看到model直接綁定到了該文本框上,是以addTodo方法可以直接根據$scope擷取文本框的屬性,完了調用單例store提供的靜态方法存儲資料,saving參數可以暫時将文本框變成不可編輯狀态,而後todo資料更新,會自動引發View變化,于是流程結束!!!
我們如果将$scope放到全局上對其資料造成變化:
window.sss = $scope;
//控制台中造成變化
sss.todos.pop()
每次傳回操作視圖時候,該變化會馬上反應到View上,于是我發現了以下不同:
① 因為所有與業務相關的資料全部做了雙向綁定,我根本沒有必要由dom擷取資料了,我自然而然的到$scope中擷取資料,不知道為什麼,這個特性讓我有點愉悅!
② 我要做的事情其實就是約定好資料對象,然後将該對象放到要用到的所有視圖上即可,每次記憶體中資料變化Dom會同步更新
于是通過以上兩點,我似乎得到了一個驚人的結論:
似乎我一旦配置好ng-model後,我要做的事情僅僅是操作$scope上的資料!!!
因為,前端要做的事情隻不過是正确的展示伺服器端的資料,每次DOM事件造成的改變也往往是資料引起的,如果我們能做到資料變化自動更新到DOM變化的話,那麼DOM操作的必要似乎沒有了,而angular幹的事情正是如此!!!
思考
到此為止,TodoMVC的代碼我雖然沒有完全看懂,但是他帶給我的震撼是全方位的,之前使用MVC類架構可以規範資料到DOM的操作,很大程度上解除DOM和JavaScript的耦合關系,而angular似乎完全抛開了業務資料導緻的DOM變化操作!!!
我們現在團隊有一mis背景系統,我在考慮是否要把它接過來,使用angular+bootstrap重構,可能别有一番風味吧!
最後,今天初步調研了一下angularJS,就已經感受到他的魅力了,後面時間需要将之用于實踐,并且對其設計思想作深入研究!!!
本文轉自葉小钗部落格園部落格,原文連結:http://www.cnblogs.com/yexiaochai/p/4855780.html,如需轉載請自行聯系原作者