1.類庫( 提供類方法 ) 和框架
類庫提供一系列的函數(shù)和方法的合集雀费,能夠加快你寫代碼的速度放前。但是主導(dǎo)邏輯的還是自己的代碼忿磅。常用的類庫 eg: jquery
框架 特殊的已經(jīng)實現(xiàn)了 web 的應(yīng)用。只需要按照其邏輯填充你的業(yè)務(wù)邏輯就能得到完整的應(yīng)用
angular 的特點
提供端對端的解決方案
構(gòu)建一個 CRUD(add retrieve update delete) 應(yīng)用的全部內(nèi)容:`數(shù)據(jù)綁定犀斋,表單驗證贝乎,路由,深度鏈接叽粹,組件重用览效,依賴注入`
測試方案: `單元測試, 端對端測試虫几,模擬和自動化測試`
具有各種種子應(yīng)用作為模板和起點
特點
angular 主要考慮構(gòu)建 CRUD 應(yīng)用锤灿,并不是所有的應(yīng)用都適合使用 angular 來構(gòu)建
例如游戲,圖形編輯界面就不適合使用 angular
angular 的標榜概念
angular 認為聲明式的代碼比命令式的代碼更加符合 構(gòu)建 (視圖 + 軟件)邏輯的代碼
聲明式的語言 :提前將所有的操作內(nèi)置辆脸,使用時只需要按照規(guī)定聲明該操作但校,語言或者機器本身可以進行構(gòu)建應(yīng)用
聲明式的語言介紹:HTML 就是聲明式的結(jié)構(gòu),比如需要某個元素居中啡氢,不需要告訴瀏覽器具體的行為(需要找到元素的中間位置状囱,將元素放在那里)术裸,只需要添加一個 align='center' 的屬性給新元素的可以了。這就是聲明式的語言
聲明式的語言也有不好的地方亭枷,就是所有可以使用的操作已經(jīng)提前內(nèi)置袭艺,所以他不能識別新的語法結(jié)構(gòu),比如你想讓元素居左 1/3 處就很難處理
將 DOM 操作和應(yīng)用邏輯解耦
將測試和開發(fā)同等看待
大幅度減少應(yīng)用中需要使用的各種 回調(diào)的邏輯叨粘,擺脫大量的回調(diào)邏輯
解放DOM 操作猾编,
對頁面的UI操作可控,例如大量的DOM事件
angular 已經(jīng)有了許多搭建好的基礎(chǔ)服務(wù)框架
angular 的初始化信息
angular 會在 DOMContentLoaded 事件觸發(fā)時執(zhí)行升敲, 通過 ng-app 指令 尋找你的應(yīng)用的根作用域
1. 首先載入和指令相關(guān)的模塊
2. 穿件應(yīng)用的 注入器(injector)
3. 將 ng-app 作為根節(jié)點編譯 DOM 答倡。
也可以使用 angular.bootstrap( 節(jié)點 ) 來手動裝載節(jié)點
2. angular 的指令
指令的定義:由一個新的屬性,元素名稱驴党,css類名等帶來DOM 樣式或者行為的改變瘪撇。
指令( angular 的行為擴展 ):HTML 編譯器,能夠識別新的 HTML 語法鼻弧,可以將行為動作關(guān)聯(lián)到HTML或者其屬性上面设江,設(shè)置可以創(chuàng)造自定義行為的元素,可復(fù)用攘轩。
注意指令是在最開始的時候被載入頁面的
指令本質(zhì)上就是一個代用功能的函數(shù) ** return 一個函數(shù) **,類比于 react 的自定義組件
** angular API 有幾個大的的分類 **
ng.function ( 功能函數(shù)码俩,類比于jquery 的方法函數(shù) )
** ng.directive( angular 的重大模塊度帮,eg: ng-model 等 ) **
** ng.provider ( 依賴注入功能 )**
.......
3. angular 的 編譯器( compiler )
編譯器通過遍歷 DOM 來查找和關(guān)聯(lián)屬性, 其分為 編譯 和 鏈接 兩個階段
編譯:遍歷所有的 DOM 收集指令稿存,生成一個 鏈接函數(shù)集合
鏈接:將指令和作用域綁定笨篷,生成一個動態(tài)的視圖姻成。
作用域模型的改變會反映到視圖上肄程,視圖的操作會反映到底作用域模型( 中間通過鏈接函數(shù)得以實現(xiàn) )
4. angular 的視圖 ( 動態(tài)的 )
視圖.png
5. angular 核心
啟動程序+執(zhí)行期+作用域+控制器 ( 應(yīng)用的行為 )+模型 ( 應(yīng)用的數(shù)據(jù) )+視圖+指令+過濾器+注入器+模塊+命名空間
angular 執(zhí)行流程.png
1. 啟動程序
concepts-startup.png
** 啟動階段主要工作是建立指令關(guān)聯(lián)關(guān)系和渲染DOM **
瀏覽器解析HTML坊饶,然后將其解析成為 DOM
瀏覽器載入 angularJS
angular 等待 DOMContentLoaded event 事件觸發(fā)
angular 找到 ng-app 指令越妈,作為應(yīng)用程序的邊界或者根作用域
使用 ng-app 中的模塊來逐個配置注入器( $injector )
注入器 ( $injector ) 是用于創(chuàng)建 “編譯服務(wù)($compile service)” 和 “根作用域( $rootScope )”垮庐。
編譯服務(wù)的作用: 首先將 DOM 和 根作用域進行鏈接
編譯服務(wù)將指令( ng-model ... ng-init...等 ) 和 作用域的變量進行一一關(guān)聯(lián)鸽心。
通過變量替換墓猎,將構(gòu)件好的視圖展現(xiàn)在頁面上
注意上面 編譯服務(wù)的作用:兩個階段:編譯階段和鏈接階段
** 注意點: **
ng-app 作為根應(yīng)用指令卷胯,首先將注入器配置在根模塊上面燕锥。( 這一步與 DOM 無關(guān) )
$injector 創(chuàng)建了 $compile 和 $rootScope
$compile 將得到的所有的根 DOM 和 $rootScope 進行關(guān)聯(lián)
2. 執(zhí)行時期 ( 主要是事件回調(diào)辜贵,響應(yīng)操作等觸發(fā)執(zhí)行 )
concepts-runtime.png
** 執(zhí)行時期主要工作內(nèi)容是 事件要被正確的執(zhí)行和渲染 **
關(guān)于執(zhí)行時期的重點概念
只有在angular 的執(zhí)行的上下文環(huán)境中才能享受到angular 提供的各種數(shù)據(jù)綁定,異常處理归形,資源管理和服務(wù)等等托慨。eg: 使用 angular 的 $setTimeOut 完成延時后可以自動更新頁面視圖
可以使用 $apply() 來從普通的JavaScript 進入 angularJs的上下文環(huán)境。只有在使用自定義的事件或者使用第三方類庫時暇榴,才需要執(zhí)行 $apply厚棵。
執(zhí)行時期的流程:
通過調(diào)用 scope.$apply( fn )? 進入angular 的上下文環(huán)境蕉世。fn 為需要在上下文中執(zhí)行的函數(shù)
angular 執(zhí)行 fn, 此函數(shù)改變應(yīng)用的狀態(tài)
angular 進入 $digest 循環(huán),$digest 由兩個小循環(huán)組成($evalAsync 隊列和$watch列表婆硬,如上圖), 該循環(huán)一直迭代狠轻,直到模型穩(wěn)定.
一個大循環(huán)由兩個小循環(huán)構(gòu)成。
模型穩(wěn)定的標志是:$evalAsync 隊列為空柿祈,$watch 列表中再無任何改變哈误。
$evalAsync 通常用于管理視圖渲染前需要在當(dāng)前框架外面執(zhí)行的操作
$watch是個表達式的集合,若檢測到有更改躏嚎,$watch 函數(shù)就會調(diào)用蜜自,將新的值更新到 DOM 中
一旦 angular 的 $digest 結(jié)束循環(huán),整個執(zhí)行就會離開 angular 和 JavaScript 的上下文環(huán)境卢佣,
最后一步重荠,瀏覽器更新界面視圖重新渲染。
3. 作用域
mvc.png
將模型整理好傳遞給視圖虚茶,將瀏覽器的動作和事件傳遞給控制器
1. 作為中介存在 ( 鏈接數(shù)據(jù)模型和數(shù)據(jù)視圖 )
2. 作用域擁有層級結(jié)構(gòu)戈鲁,此層級結(jié)構(gòu)和 DOM 的層級結(jié)構(gòu)相互對應(yīng)
3. 每一個作用域都有獨立的上下文環(huán)境
作用域的特點:
1. 作用域提供 API ( $watch 來觀察模型的變化 )
2. 作用域提供 API ( $apply ) 將任何模型的改變從 angular 領(lǐng)域 通過系統(tǒng)映射到視圖上
3. 作用域通過共享模型成員的方法嵌套到應(yīng)用組件上面,一個作用域從父作用域繼承屬性
4. 作用域提供表達式執(zhí)行的上下文環(huán)境
控制器和視圖都持有對作用域的引用嘹叫,但是控制器和視圖之間沒有任何關(guān)系婆殿。
作用域的事件傳遞:
作用域的事件傳遞和 DOM 的事件傳遞類似,事件可以廣播給子作用域罩扇,也可以傳遞給父作用域婆芦。
作用域的聲明周期
1. 創(chuàng)建: 根作用域在應(yīng)用被 $injector 啟動的時候被創(chuàng)建,在模板鏈接階段喂饥,有些執(zhí)行會自動創(chuàng)建新的作用域 ( eg:ng-repeat )
2. 觀察者注冊:模板鏈接階段消约,指令會在作用域上注冊觀察者,觀察者用于將 DOM 的改變傳遞給 DOM
3. 模型改版: 只有在 scope.$apply() 中變化的數(shù)據(jù)才能被準確反映到模型上
angular 本身的 API 會自動應(yīng)用 apply员帮,eg: $http $timeout 不需要額外的 $apply
4. 變化的觀測:在 $apply 的最后或粮,angular 會在根作用域執(zhí)行一個 $digest 循環(huán),將所有的變化傳遞給子作用域捞高,只要在 $digest 循環(huán)中的所有表達式和函數(shù)都會被檢測氯材,用于觀察模型的變化。
模型的變化檢測機制就是 angular 的 臟檢查機制
4. 控制器
控制器用于構(gòu)造視圖的控制代碼棠枉,主要作用就是構(gòu)造數(shù)據(jù)模型浓体。
控制器的特點 (? 控制器應(yīng)該和視圖做到分離 )
1. 控制器是由 JavaScript 書寫,控制器不應(yīng)該包含任何 HTML 代碼辈讶。
2. 視圖使用 HTML 書寫的命浴,視圖不應(yīng)該包含任何 JavaScript 代碼。
3. 控制器和視圖沒有直接的關(guān)系。所以可以使用一個控制器對應(yīng)多個視圖生闲。
控制器的三個作用( 書寫媳溺,分清 c 和 v 的區(qū)別 )
1. 在應(yīng)用中設(shè)置模型的初始狀態(tài),
2. 將整理好的模型和函數(shù)交給作用域( scope )
3. 監(jiān)聽模型的變化并響應(yīng)事件或者動作( 事件響應(yīng)函數(shù) )
5. 模型
模型就是和模板結(jié)合生成視圖
模板就是單純的 HTML 代碼碍讯,
模型的特點:
模型必須使用作用域來引用
模型可以是任何 JavaScript 類型的數(shù)據(jù)
mvc.png
說明:
控制器和 視圖沒有直接的關(guān)系
mvc 的核心是由作用域承擔(dān)起來的
一個控制器可以對應(yīng)多個模型
6. angular 的 watch, apply, digest
$watch
$watch 隊列是在 UI 上使用了一個指令時悬蔽,就自動生成一條 $watch,所有的 $watch 組合成為一個 $watch 列表,
$watch 列表是在編譯的時候就生成完畢
{{person.name}} - {{person.age}}
對于上述 ng-repeat? 若有 10 個 people 會生成 10 * 2 +1 個 $watch
*** $watch 參數(shù)詳解 ***
使用 $watch 函數(shù)可以進行 自定義的 操作監(jiān)聽捉兴,更改 視圖
$scope.$watch('name', function(newValue, oldValue) {
if (newValue === oldValue) { return; } // AKA first run
$scope.updated++;
});
$watch 的 第二個參數(shù)是一個 函數(shù)蝎困,用于 監(jiān)聽 前面的變量是否更改。
$scope.$watch('user', function(newValue, oldValue) {
if (newValue === oldValue) { return; }
$scope.updated++;
}, true);
$watch 的 第三個參數(shù)是 boolear 類型的值倍啥,
** 作用:**
** newValue 和 oldValue 默認是比較 新舊值的引用禾乘,若 user 是一個對象,則無法判斷對象內(nèi)部值的改變,需要使用第三個參數(shù)來進行判斷對象內(nèi)部深層次的值是否改變虽缕。**
$digest
$digest 循環(huán)過程 會 包含兩個小循環(huán)始藕,$evalAsync 和 $watch 隊列循環(huán)。
$digest 會涉及到臟檢查機制氮趋,反復(fù)詢問 $watch 隊列是否有數(shù)據(jù)改變
{{ name }}
Change the name
// 這里有 一個 $watch( name 會生成 $watch 伍派, 而 ng-click 不會生成 $watch, 因為函數(shù) 是不會變的。
我們按下按鈕
瀏覽器接收到一個事件剩胁,進入angular context(后面會解釋為什么)诉植。
$digest循環(huán)開始執(zhí)行,查詢每個$watch是否變化昵观。
由于監(jiān)視$scope.name的$watch報告了變化倍踪,它會強制再執(zhí)行一次$digest循環(huán)。
新的$digest循環(huán)沒有檢測到變化索昂。
瀏覽器拿回控制權(quán),更新與$scope.name新值相應(yīng)部分的DOM
$apply 用于進入 angular 的 上下文環(huán)境扩借, 只是一個angular 提供的一個 api 沒有 循環(huán)等
$apply 會自動觸發(fā) $digest 循環(huán)
angular 自己的事件動作會自動觸發(fā) $apply
非 angular 環(huán)境需要手動觸發(fā) $apply
有時自己添加了額外的 操作或動作椒惨,需要手動 $apply() 執(zhí)行 $digest 循環(huán)( 典型:訪問服務(wù)器以后的回調(diào)操作 )
element.bind('click', function() {
scope.foo++;
scope.bar++;
scope.$apply();? ? ? // 手動觸發(fā),進行一次 $digest 循環(huán)
});
// 第二種
element.bind('click', function() {? ? //
scope.$apply(function() {? ? ? 使用 $apply 函數(shù)進行 $digest 循環(huán)
scope.foo++;
scope.bar++;
});
})
angular 執(zhí)行時期 再解釋 ( 主要是 $apply $digest $watch 這三個方法的流程 )
concepts-runtime.png
1. 頁面觸發(fā) DOM 事件潮罪,回調(diào)等康谆。
2. 應(yīng)用程序自動調(diào)用 $apply()? 方法進入 angular 上下文,觸發(fā) $digest 循環(huán)開始嫉到,
3. $digest 循環(huán) 遍歷 $watch 列表集合沃暗,臟檢查 數(shù)據(jù)模型的改變,循環(huán)過程
4. 檢查完畢何恶,更新 數(shù)據(jù)模型孽锥,( 更改變量或者其他動作操作 )
5. 作用域根據(jù)數(shù)據(jù)模型渲染 UI視圖。
6. 繼續(xù)監(jiān)聽。
7. angular 的 通訊
angular 模塊之間的通訊方式
1. 作用域傳遞( 父子模塊通訊 )$parent $child
2. 作用域數(shù)據(jù)傳遞 + $watch ( 類似于指令的數(shù)據(jù)傳遞的 = )
3. 事件廣播 ( $emit $on $boardcast )
使用 $rootscope
將 $rootscope 作為依賴注入項惜辑,在子組件中使用 $rootscope
使用場景: 對于一處改變唬涧,需要多處通知更改的變量,頻繁的調(diào)用可以使用 $rootscope(對于登錄使用的用戶名稱盛撑,在一個地方使用碎节,需要多處顯示,并且更改后需要多處更改)
myAppModule.controller('myCtrl', function($scope, $rootScope) {
$scope.change = function() {
$scope.test = new Date();
};
$scope.getOrig = function() {
return $rootScope.test;
};
})
作用域繼承 的 模塊通訊
作用域繼承是在子模塊中可以直接使用父模塊方法/變量的 通訊方式
** 只適合數(shù)據(jù)從 父模塊傳遞到子模塊 **
** 在指令的數(shù)據(jù)傳遞中 通常使用這種模式進行通訊 **
Do
特點:
只適合數(shù)據(jù)從 父模塊傳遞到子模塊
子模塊的同名方法會覆蓋父模塊的 方法/變量
不能進行同級組件之間的數(shù)據(jù)傳遞抵卫。除非顯示更改 $rootscope
層級較多時 維護比較麻煩
作用域通訊 + $watch
作用域的數(shù)據(jù)只能從 父作用域傳遞到子作用域狮荔,** 使用 $watch() 可以監(jiān)控子作用于數(shù)據(jù)的改變,類似于 指令數(shù)據(jù)傳遞的 = ( 等于號 ) **
.controller("Parent", function($scope){
$scope.VM = {a: "a", b: "b"};
$scope.$watch("VM.a", function(newVal, oldVal){
// code
});
})
// 需要用到 $parent 等
.controller('Child', function($scope){
$scope.$parent.$watch('$scope.VM.a', function() { ..... })
})
消息機制
scope 提供了冒泡和隧道機制介粘,$on, $emit, $boardcast
$boardcast 將事件廣播給所有的子組件殖氏,
$on 用于注冊事件函數(shù),
$emit 用于事件向上冒泡傳遞
優(yōu)缺點: 相比于 $emit碗短,$boardcast 需要向所有的子組件廣播組件的改變受葛,會消耗更多的資源
** 所以 在應(yīng)用的通訊數(shù)據(jù)達到很大體量 **
$rootscope $scope + watch 都可以成為設(shè)計的基本手法
使用 Service 進行通訊
因為 angular 中所有的 Service 都是單例的,使用 Service 能夠比 時間隧道機制在邏輯上更加清晰偎谁。
簡單抽取基本的數(shù)據(jù)
var myApp = angular.module('myApp', []);
myApp.factory('Data', function() {
return { message: "I'm data from a service" }
})
function FirstCtrl($scope, Data) {
$scope.data = Data;
}
function SecondCtrl($scope, Data) {
$scope.data = Data;
}
進階版一: 使用 $watch 來監(jiān)測數(shù)據(jù)變化
angular.module('Store', [])
//? 提供基本數(shù)據(jù)模型初始數(shù)據(jù)的 Service
.factory('Products', function() {
return {
query: function() {
return [{ name: 'Lightsaber', price: 1299.99}, { name: 'Jet Pack', price: 9999.99}, { name: 'Speeder', price: 24999.99}];
}
};
})
//? 提供數(shù)據(jù)模型的 Service
.factory('Order', function() {
var add = function(item, qty) {
item.qty = qty;
this.items.push(item);
};
var remove = function(item) {
if (this.items.indexOf(item) > -1) {
this.items.splice(this.items.indexOf(item), 1);
}
};
var total = function() {
return this.items.reduce(function(memo, item) {
return memo + (item.qty * item.price);
}, 0);
};
return {? ? ? ? // 返回完整的數(shù)據(jù)模型
items: [],
addToOrder: add,
removeFromOrder: remove,
totalPrice: total
};
}).controller('OrderCtrl', function(Products, Order) {
this.products = Products.query();
this.items = Order.items;
this.addToOrder = function(item) {
Order.addToOrder(item, 1);
};
this.removeFromOrder = function(item) {
Order.removeFromOrder(item);
};
this.totalPrice = function() {
return Order.total();
};
}).controller('CartCtrl', function($scope, Order) {
$scope.items = Order.items;
$scope.$watchCollection('items', function() {
$scope.totalPrice = Order.totalPrice().toFixed(2);
}.bind(this));
});
//? 整個頁面只有這里需要根據(jù)數(shù)據(jù)模型的變化而 改變視圖 UI
{{product.name}} - {{product.price}}
Add
Remove
angularjs的$watch总滩、$watchGroup、$watchCollection 使用方式 區(qū)別
var myModule = angular.module('myModule', []);
myModule.factory('mySharedService', function($rootScope) {
var sharedService = {};
sharedService.message = '';
sharedService.prepForBroadcast = function(msg) {
this.message = msg;
this.broadcastItem();? ? ? // 執(zhí)行 廣播
};
sharedService.broadcastItem = function() {
$rootScope.$broadcast('handleBroadcast');
};
return sharedService;
});
function ControllerZero($scope, sharedService) {
$scope.handleClick = function(msg) {
sharedService.prepForBroadcast(msg);? ? ? ? // 調(diào)用包含廣播的函數(shù)
};
$scope.$on('handleBroadcast', function() {
$scope.message = sharedService.message;
});
}
function ControllerOne($scope, sharedService) {
$scope.$on('handleBroadcast', function() {
$scope.message = 'ONE: ' + sharedService.message;
});
}
function ControllerTwo($scope, sharedService) {
$scope.$on('handleBroadcast', function() {
$scope.message = 'TWO: ' + sharedService.message;
});
}
ControllerZero.$inject = ['$scope', 'mySharedService'];
ControllerOne.$inject = ['$scope', 'mySharedService'];
ControllerTwo.$inject = ['$scope', 'mySharedService'];
作者:南航
鏈接:http://www.reibang.com/p/959cb6bb7036
來源:簡書
著作權(quán)歸作者所有巡雨。商業(yè)轉(zhuǎn)載請聯(lián)系作者獲得授權(quán)闰渔,非商業(yè)轉(zhuǎn)載請注明出處。