1.類庫( 提供類方法 ) 和框架
類庫提供一系列的函數和方法的合集,能夠加快你寫代碼的速度。但是主導邏輯的還是自己的代碼。常用的類庫 eg: jquery
框架 特殊的已經實現了 web 的應用。只需要按照其邏輯填充你的業務邏輯就能得到完整的應用
angular 的特點
提供端對端的解決方案
構建一個 CRUD(add retrieve update delete) 應用的全部內容:`數據綁定,表單驗證,路由,深度鏈接,組件重用,依賴注入`
測試方案: `單元測試, 端對端測試,模擬和自動化測試`
具有各種種子應用作為模板和起點
特點
angular 主要考慮構建 CRUD 應用,并不是所有的應用都適合使用 angular 來構建
例如游戲,圖形編輯界面就不適合使用 angular
angular 的標榜概念
angular 認為聲明式的代碼比命令式的代碼更加符合 構建 (視圖 + 軟件)邏輯的代碼
聲明式的語言 :提前將所有的操作內置,使用時只需要按照規定聲明該操作,語言或者機器本身可以進行構建應用
聲明式的語言介紹:HTML 就是聲明式的結構,比如需要某個元素居中,不需要告訴瀏覽器具體的行為(需要找到元素的中間位置,將元素放在那里),只需要添加一個 align='center' 的屬性給新元素的可以了。這就是聲明式的語言
聲明式的語言也有不好的地方,就是所有可以使用的操作已經提前內置,所以他不能識別新的語法結構,比如你想讓元素居左 1/3 處就很難處理
將 DOM 操作和應用邏輯解耦
將測試和開發同等看待
大幅度減少應用中需要使用的各種 回調的邏輯,擺脫大量的回調邏輯
解放DOM 操作,
對頁面的UI操作可控,例如大量的DOM事件
angular 已經有了許多搭建好的基礎服務框架
angular 的初始化信息
angular 會在 DOMContentLoaded 事件觸發時執行, 通過 ng-app 指令 尋找你的應用的根作用域
1. 首先載入和指令相關的模塊
2. 穿件應用的 注入器(injector)
3. 將 ng-app 作為根節點編譯 DOM 。
也可以使用 angular.bootstrap( 節點 ) 來手動裝載節點
2. angular 的指令
指令的定義:由一個新的屬性,元素名稱,css類名等帶來DOM 樣式或者行為的改變。
指令( angular 的行為擴展 ):HTML 編譯器,能夠識別新的 HTML 語法,可以將行為動作關聯到HTML或者其屬性上面,設置可以創造自定義行為的元素,可復用。
注意指令是在最開始的時候被載入頁面的
指令本質上就是一個代用功能的函數 ** return 一個函數 **,類比于 react 的自定義組件
** angular API 有幾個大的的分類 **
ng.function ( 功能函數,類比于jquery 的方法函數 )
** ng.directive( angular 的重大模塊,eg: ng-model 等 ) **
** ng.provider ( 依賴注入功能 )**
.......
3. angular 的 編譯器( compiler )
編譯器通過遍歷 DOM 來查找和關聯屬性, 其分為 編譯 和 鏈接 兩個階段
編譯:遍歷所有的 DOM 收集指令,生成一個 鏈接函數集合
鏈接:將指令和作用域綁定,生成一個動態的視圖。
作用域模型的改變會反映到視圖上,視圖的操作會反映到底作用域模型( 中間通過鏈接函數得以實現 )
4. angular 的視圖 ( 動態的 )
視圖.png
5. angular 核心
啟動程序+執行期+作用域+控制器 ( 應用的行為 )+模型 ( 應用的數據 )+視圖+指令+過濾器+注入器+模塊+命名空間
angular 執行流程.png
1. 啟動程序
concepts-startup.png
** 啟動階段主要工作是建立指令關聯關系和渲染DOM **
瀏覽器解析HTML,然后將其解析成為 DOM
瀏覽器載入 angularJS
angular 等待 DOMContentLoaded event 事件觸發
angular 找到 ng-app 指令,作為應用程序的邊界或者根作用域
使用 ng-app 中的模塊來逐個配置注入器( $injector )
注入器 ( $injector ) 是用于創建 “編譯服務($compile service)” 和 “根作用域( $rootScope )”。
編譯服務的作用: 首先將 DOM 和 根作用域進行鏈接
編譯服務將指令( ng-model ... ng-init...等 ) 和 作用域的變量進行一一關聯。
通過變量替換,將構件好的視圖展現在頁面上
注意上面 編譯服務的作用:兩個階段:編譯階段和鏈接階段
** 注意點: **
ng-app 作為根應用指令,首先將注入器配置在根模塊上面。( 這一步與 DOM 無關 )
$injector 創建了 $compile 和 $rootScope
$compile 將得到的所有的根 DOM 和 $rootScope 進行關聯
2. 執行時期 ( 主要是事件回調,響應操作等觸發執行 )
concepts-runtime.png
** 執行時期主要工作內容是 事件要被正確的執行和渲染 **
關于執行時期的重點概念
只有在angular 的執行的上下文環境中才能享受到angular 提供的各種數據綁定,異常處理,資源管理和服務等等。eg: 使用 angular 的 $setTimeOut 完成延時后可以自動更新頁面視圖
可以使用 $apply() 來從普通的JavaScript 進入 angularJs的上下文環境。只有在使用自定義的事件或者使用第三方類庫時,才需要執行 $apply。
執行時期的流程:
通過調用 scope.$apply( fn )? 進入angular 的上下文環境。fn 為需要在上下文中執行的函數
angular 執行 fn, 此函數改變應用的狀態
angular 進入 $digest 循環,$digest 由兩個小循環組成($evalAsync 隊列和$watch列表,如上圖), 該循環一直迭代,直到模型穩定.
一個大循環由兩個小循環構成。
模型穩定的標志是:$evalAsync 隊列為空,$watch 列表中再無任何改變。
$evalAsync 通常用于管理視圖渲染前需要在當前框架外面執行的操作
$watch是個表達式的集合,若檢測到有更改,$watch 函數就會調用,將新的值更新到 DOM 中
一旦 angular 的 $digest 結束循環,整個執行就會離開 angular 和 JavaScript 的上下文環境,
最后一步,瀏覽器更新界面視圖重新渲染。
3. 作用域
mvc.png
將模型整理好傳遞給視圖,將瀏覽器的動作和事件傳遞給控制器
1. 作為中介存在 ( 鏈接數據模型和數據視圖 )
2. 作用域擁有層級結構,此層級結構和 DOM 的層級結構相互對應
3. 每一個作用域都有獨立的上下文環境
作用域的特點:
1. 作用域提供 API ( $watch 來觀察模型的變化 )
2. 作用域提供 API ( $apply ) 將任何模型的改變從 angular 領域 通過系統映射到視圖上
3. 作用域通過共享模型成員的方法嵌套到應用組件上面,一個作用域從父作用域繼承屬性
4. 作用域提供表達式執行的上下文環境
控制器和視圖都持有對作用域的引用,但是控制器和視圖之間沒有任何關系。
作用域的事件傳遞:
作用域的事件傳遞和 DOM 的事件傳遞類似,事件可以廣播給子作用域,也可以傳遞給父作用域。
作用域的聲明周期
1. 創建: 根作用域在應用被 $injector 啟動的時候被創建,在模板鏈接階段,有些執行會自動創建新的作用域 ( eg:ng-repeat )
2. 觀察者注冊:模板鏈接階段,指令會在作用域上注冊觀察者,觀察者用于將 DOM 的改變傳遞給 DOM
3. 模型改版: 只有在 scope.$apply() 中變化的數據才能被準確反映到模型上
angular 本身的 API 會自動應用 apply,eg: $http $timeout 不需要額外的 $apply
4. 變化的觀測:在 $apply 的最后,angular 會在根作用域執行一個 $digest 循環,將所有的變化傳遞給子作用域,只要在 $digest 循環中的所有表達式和函數都會被檢測,用于觀察模型的變化。
模型的變化檢測機制就是 angular 的 臟檢查機制
4. 控制器
控制器用于構造視圖的控制代碼,主要作用就是構造數據模型。
控制器的特點 (? 控制器應該和視圖做到分離 )
1. 控制器是由 JavaScript 書寫,控制器不應該包含任何 HTML 代碼。
2. 視圖使用 HTML 書寫的,視圖不應該包含任何 JavaScript 代碼。
3. 控制器和視圖沒有直接的關系。所以可以使用一個控制器對應多個視圖。
控制器的三個作用( 書寫,分清 c 和 v 的區別 )
1. 在應用中設置模型的初始狀態,
2. 將整理好的模型和函數交給作用域( scope )
3. 監聽模型的變化并響應事件或者動作( 事件響應函數 )
5. 模型
模型就是和模板結合生成視圖
模板就是單純的 HTML 代碼,
模型的特點:
模型必須使用作用域來引用
模型可以是任何 JavaScript 類型的數據
mvc.png
說明:
控制器和 視圖沒有直接的關系
mvc 的核心是由作用域承擔起來的
一個控制器可以對應多個模型
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 參數詳解 ***
使用 $watch 函數可以進行 自定義的 操作監聽,更改 視圖
$scope.$watch('name', function(newValue, oldValue) {
if (newValue === oldValue) { return; } // AKA first run
$scope.updated++;
});
$watch 的 第二個參數是一個 函數,用于 監聽 前面的變量是否更改。
$scope.$watch('user', function(newValue, oldValue) {
if (newValue === oldValue) { return; }
$scope.updated++;
}, true);
$watch 的 第三個參數是 boolear 類型的值,
** 作用:**
** newValue 和 oldValue 默認是比較 新舊值的引用,若 user 是一個對象,則無法判斷對象內部值的改變,需要使用第三個參數來進行判斷對象內部深層次的值是否改變。**
$digest
$digest 循環過程 會 包含兩個小循環,$evalAsync 和 $watch 隊列循環。
$digest 會涉及到臟檢查機制,反復詢問 $watch 隊列是否有數據改變
{{ name }}
Change the name
// 這里有 一個 $watch( name 會生成 $watch , 而 ng-click 不會生成 $watch, 因為函數 是不會變的。
我們按下按鈕
瀏覽器接收到一個事件,進入angular context(后面會解釋為什么)。
$digest循環開始執行,查詢每個$watch是否變化。
由于監視$scope.name的$watch報告了變化,它會強制再執行一次$digest循環。
新的$digest循環沒有檢測到變化。
瀏覽器拿回控制權,更新與$scope.name新值相應部分的DOM
$apply 用于進入 angular 的 上下文環境, 只是一個angular 提供的一個 api 沒有 循環等
$apply 會自動觸發 $digest 循環
angular 自己的事件動作會自動觸發 $apply
非 angular 環境需要手動觸發 $apply
有時自己添加了額外的 操作或動作,需要手動 $apply() 執行 $digest 循環( 典型:訪問服務器以后的回調操作 )
element.bind('click', function() {
scope.foo++;
scope.bar++;
scope.$apply();? ? ? // 手動觸發,進行一次 $digest 循環
});
// 第二種
element.bind('click', function() {? ? //
scope.$apply(function() {? ? ? 使用 $apply 函數進行 $digest 循環
scope.foo++;
scope.bar++;
});
})
angular 執行時期 再解釋 ( 主要是 $apply $digest $watch 這三個方法的流程 )
concepts-runtime.png
1. 頁面觸發 DOM 事件,回調等。
2. 應用程序自動調用 $apply()? 方法進入 angular 上下文,觸發 $digest 循環開始,
3. $digest 循環 遍歷 $watch 列表集合,臟檢查 數據模型的改變,循環過程
4. 檢查完畢,更新 數據模型,( 更改變量或者其他動作操作 )
5. 作用域根據數據模型渲染 UI視圖。
6. 繼續監聽。
7. angular 的 通訊
angular 模塊之間的通訊方式
1. 作用域傳遞( 父子模塊通訊 )$parent $child
2. 作用域數據傳遞 + $watch ( 類似于指令的數據傳遞的 = )
3. 事件廣播 ( $emit $on $boardcast )
使用 $rootscope
將 $rootscope 作為依賴注入項,在子組件中使用 $rootscope
使用場景: 對于一處改變,需要多處通知更改的變量,頻繁的調用可以使用 $rootscope(對于登錄使用的用戶名稱,在一個地方使用,需要多處顯示,并且更改后需要多處更改)
myAppModule.controller('myCtrl', function($scope, $rootScope) {
$scope.change = function() {
$scope.test = new Date();
};
$scope.getOrig = function() {
return $rootScope.test;
};
})
作用域繼承 的 模塊通訊
作用域繼承是在子模塊中可以直接使用父模塊方法/變量的 通訊方式
** 只適合數據從 父模塊傳遞到子模塊 **
** 在指令的數據傳遞中 通常使用這種模式進行通訊 **
Do
特點:
只適合數據從 父模塊傳遞到子模塊
子模塊的同名方法會覆蓋父模塊的 方法/變量
不能進行同級組件之間的數據傳遞。除非顯示更改 $rootscope
層級較多時 維護比較麻煩
作用域通訊 + $watch
作用域的數據只能從 父作用域傳遞到子作用域,** 使用 $watch() 可以監控子作用于數據的改變,類似于 指令數據傳遞的 = ( 等于號 ) **
.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 用于注冊事件函數,
$emit 用于事件向上冒泡傳遞
優缺點: 相比于 $emit,$boardcast 需要向所有的子組件廣播組件的改變,會消耗更多的資源
** 所以 在應用的通訊數據達到很大體量 **
$rootscope $scope + watch 都可以成為設計的基本手法
使用 Service 進行通訊
因為 angular 中所有的 Service 都是單例的,使用 Service 能夠比 時間隧道機制在邏輯上更加清晰。
簡單抽取基本的數據
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 來監測數據變化
angular.module('Store', [])
//? 提供基本數據模型初始數據的 Service
.factory('Products', function() {
return {
query: function() {
return [{ name: 'Lightsaber', price: 1299.99}, { name: 'Jet Pack', price: 9999.99}, { name: 'Speeder', price: 24999.99}];
}
};
})
//? 提供數據模型的 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 {? ? ? ? // 返回完整的數據模型
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));
});
//? 整個頁面只有這里需要根據數據模型的變化而 改變視圖 UI
{{product.name}} - {{product.price}}
Add
Remove
angularjs的$watch、$watchGroup、$watchCollection 使用方式 區別
var myModule = angular.module('myModule', []);
myModule.factory('mySharedService', function($rootScope) {
var sharedService = {};
sharedService.message = '';
sharedService.prepForBroadcast = function(msg) {
this.message = msg;
this.broadcastItem();? ? ? // 執行 廣播
};
sharedService.broadcastItem = function() {
$rootScope.$broadcast('handleBroadcast');
};
return sharedService;
});
function ControllerZero($scope, sharedService) {
$scope.handleClick = function(msg) {
sharedService.prepForBroadcast(msg);? ? ? ? // 調用包含廣播的函數
};
$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.lxweimin.com/p/959cb6bb7036
來源:簡書
著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。