1.背景介紹
1、AngularJS Scope(作用域)
Scope(作用域)是應用在 HTML (視圖)和 JavaScript (控制器)之間的紐帶。Scope是一個對象,有可用的方法和屬性。Scope可應用在視圖和控制器上。$scope的使用貫穿整個 Angular App應用,它與數據模型相關聯,同時也是表達式執行的上下文.有了 $scope就在視圖和控制器之間建立了一個通道,基于作用域視圖在修改數據時會立刻更新 $scope,同樣的 $scope發生改變時也會立刻重新渲染視圖.
2、根作用域 rootScope
所有的應用都有一個 $rootScope,它可以作用在 ng-app指令包含的所有 HTML元素中。$rootScope可作用于整個應用中。是各個 controller中 scope的橋梁。用 rootscope定義的值,可以在各個 controller中使用
2.知識剖析
? ? ? 1、$scope
$scope是一個把view(一個DOM元素)連結到controller上的對象。在我們的MVC結構里,這個 $scope將成為model,它提供一個綁定到DOM元素(以及其子元素)上的excecution context。
$scope實際上就是一個JavaScript對象,controller和view都可以訪問它,所以我們可以利用它在兩者間傳遞信息。在這個 $scope對象里,我們既可以存儲數據,又可以存儲將要運行在view上的函數。每一個Angular應用都會有一個$rootScope。這個$rootScope 是最頂級的scope,它對應著含有ng-app 指令屬性的那個DOM元素。如果頁面上沒有明確設定$scope ,Angular 就會把數據和函數都綁定到這里。
Angular應用啟動并生成視圖時,會將根 ng-app元素與 $rootScope進行綁定.$rootScope是所有 $scope的最上層對象,可以理解為一個 Angular應用中得全局作用域對象,所以不應該附加太多邏輯或者變量給$rootScope,和污染 Javascript全局作用域是一樣的道理.
$scope的作用
$scope對象在 Angular中充當數據模型的作用,也就是一般 MVC框架中 Model得角色.但又不完全與通常意義上的數據模型一樣,因為 $scope并不處理和操作數據,它只是建立了視圖和 HTML之間的橋梁,讓視圖和 Controller之間可以友好的通訊。
它有如下作用和功能:
提供了觀察者可以監聽數據模型的變化
可以將數據模型的變化通知給整個 App
可以進行嵌套,隔離業務功能和數據
給表達式提供上下文執行環境
在 Javascript中創建一個新的執行上下文,實際就是用函數創建了一個新的本地上下文,
在 Angular中當為子 DOM元素創建新的作用域時,其實就是為子 DOM元素創建了一個新的執行上下文.
$scope的生命周期有4個階段:
1.創建
控制器或者指令創建時, Angular會使用 $injector創建一個新的作用域,然后在控制器或指令運行時,將作用域傳遞進去.
2.鏈接
Angular啟動后會將所有 $scope對象附加或者說鏈接到視圖上,所有創建 $scope對象的函數也會被附加到視圖上.
這些作用域將會注冊當 Angular上下文發生變化時需要運行的函數.也就是 $watch函數, Angular通過這些函數或者何時開始事件循環.
3.更新
一旦事件循環開始運行,就會開始執行自己的臟值檢測.一旦檢測到變化,就會觸發 $scope上指定的回調函數
4.銷毀
通常來講如果一個 $scope在視圖中不再需要, Angular會自己清理它.
ng-controller指令給所在的DOM元素創建了一個新的$scope對象,并將這個$scope對象包含進外層DOM元素的$scope對象里。
在ng-app里,這個外層DOM元素的$scope對象,就是$rootScope對象。這個scope鏈是這樣的:
所有scope都遵循原型繼承(prototypal inheritance),這意味著它們都能訪問父scope們。對任何屬性和方法,如果AngularJS在當前scope上找不到,就會到父scope上去找,如果在父scope上也沒找到,就會繼續向上回溯,一直到$rootScope上。唯一的例外:有些指令屬性可以選擇性地創建一個獨立的scope,讓這個scope不繼承它的父scope們。
3、$watch:
? ? ? ? angularjs核心之一是雙向綁定,那么這個雙向綁定是如何實現的呢?? 當我們在創建出scope下的一個新屬性的時候,ng就會主動為我們新屬性注冊$watch這個方法,$watch用來監聽的數據變化,當數據變化之后,就立即把view和scope上數據同步。AngularJS就能夠自動注冊并監聽變量的改變。AngularJS會首先將在{{ }}中聲明的表達式編譯成函數并調用$watch方法。
?$watch是一個scope函數,用于監聽模型變化
?$watch(watchExpression, listener, objectEquality){ ... };
?watchExpression:$watch方法的第一個參數是一個函數,它通常被稱為watch函數,它的返回值聲明需要監聽的變量;
?listener:第二個參數是listener,在變量發生改變的時候會被調用。和傳統的事件注冊和監聽沒有什么本質上的差別,差別僅在于AngularJS能夠自動注冊絕大多數的change事件并進行監聽,只要按照AngularJS要求的語法來寫HTML中的表達式代碼,即{{ }}。 $watch方法為當前scope注冊了一個watcher,這個watcher會被保存到一個scope內部維護的數組中,即是$$watchers。 watcher的主要目的是對scope上的某個屬性進行監控
? ? objectEquality:是否深度監聽,如果設置為true,它告訴Angular檢查所監控的對象中每一個屬性的變化.? ?當瀏覽器接收到可以被angular context處理的事件時,$digest循環就會觸發。這個循環是由兩個更小的循環組合起來的。? ? 一個處理evalAsync隊列(這個沒有探究),另一個處理$watch隊列。$digest將會遍歷我們的$watch隊列。如果有至少一個更新過, 這個循環就會再次觸發,直到所有的$watch都沒有變化。這樣就能夠保證每個model都已經不會再變化。 如果循環超過10次的話,它將會拋出一個異常,防止無限循環。每次當$digest循環結束時,DOM相應地變化。
? ? 例如我們按下按鈕觸發ng-click事件:
? 1、瀏覽器接收到一個事件,進入angular context。
?2、 $digest循環開始執行,查詢每個$watch是否變化。
? 3、 由于監視$scope.name的$watch報告了變化,它會強制再執行一次$digest循環。
? 4、 新的$digest循環沒有檢測到變化。
? ?5、瀏覽器拿回控制權,更新與$scope.name新值相應部分的DOM。
? ? 6、這里重要的是每一個進入angular context的事件都會執行一個$digest循環,也就是說每次我們輸入一個字母循環都會檢查整個頁面的所有$watch。? Angular會為我們自動調用$apply!因此當點擊帶有ng-click的元素時,事件就會被封裝到一個$apply調用。? 比如有一個ng-model="foo"的輸入框,然后敲一個f,事件就會這樣調用$apply("foo = 'f';"),觸發$digest循環。
4、$state
? ? $state是ui-rooter的一項服務負責表示狀態以及它們之間的轉換。它還提供了接口來詢問當前狀態
常用的方法有:
$state.go(to, params, options) :轉換到新狀態的方便方法
$state.includes(stateOrName, params, options) :確定當前活動狀態是否等于或是狀態狀態子的方法。返回布爾值
$state.params: 返回狀態參數的對象$stateParams
$stateParams是一個對象,包含 url中每個參數的鍵/值。$stateParams可以為控制器或者服務提供 url的各個部分。
? ? 注意:$stateParams必須與一個控制器相關,并且$stateParams中的“鍵/值”也必須事先在那個控制器的url屬性中有定義。
3.常見問題
1、 如何自定義$watch?
2、 什么時候需要我們去調用$watch?
4.解決方案
1、自定義自己的watches:
? ? angular.module("myApp",[]).controller('MainCtrl', function($scope) {$scope.name = "hello";$scope.updated = -1;$scope.$watch('name', function() {$scope.updated++;});});
//創造一個新的$watch的方法。第一個參數是一個字符串或者函數,在這里是只是一個字符串,就是我們要監視的變量的名字,
//第二個參數是當$watch說我監視的表達式發生變化后要執行的。當controller執行到這個$watch時,它會立即執行一次
2、 取消 $watch :
$watch會影響性能問題,特別是在移動設備上,在不需要時應該清除
$watch函數本身返回一個函數,所以,當$watch不再需要的時候,我們只需調用返回的函數即可:
.controller('MainCtrl',?function($scope)?{
$scope.updated?=?0;
$scope.stop?=?function()?{
textWatch();
};
var?textWatch?=?$scope.$watch('text',?function(newVal,?oldVal)?{
if?(newVal?===?oldVal)?{?return;?}
$scope.updated++;
});
});
2、 什么時候需要我們去調用$watch?
被調用的事件沒有進入angular context,$digest循環永遠沒有執行。這種情況一般出現在指令的隔離作用域中
,也會出現在異步執行的函數體中。調用$watch需要通過$apply。
6.擴展思考
指令中的scope三個值有什么用?
? ?false 共享作用域
? ? ? true 創建自己的作用域,并繼承父作用域
? ? ? {}創建隔離作用域
7.參考文獻
參考一:? Angular.js中使用$watch監聽模型變化 http://yuankeqiang.lofter.com/post/8de51_1454f93
參考二:關于$watch應用的一些小技巧?http://blog.csdn.net/u010451286/article/details/50635839
參考三: how the apply runs a digest? :http://angular-tips.com/blog/2013/08/watch-how-the-apply-runs-a-digest
參考四:深入解析AngularJS框架中$scope的作用與生命周期?http://www.jb51.net/article/80492.htm
參考五:-@ui-router——$state服務原版詳解? https://www.cnblogs.com/koleyang/p/4576419.html
問題:
1、如何移除不必要的$watch?
.controller('MainCtrl',?function($scope)?{
$scope.updated?=?0;
$scope.stop?=?function()?{
textWatch();
};
var?textWatch?=?$scope.$watch('text',?function(newVal,?oldVal)?{
if?(newVal?===?oldVal)?{?return;?}
$scope.updated++;
});
});
2、1 ui-sref、$state.go 的區別ui-sref 一般使用在...;消息中心$state.go('someState')一般使用在 controller里面;.controller('firstCtrl', function($scope, $state) { $state.go('login'); });這兩個本質上是一樣的東西,我們看ui-sref的源碼:...element.bind("click", function(e) { var button = e.which || e.button; if ( !(button > 1 || e.ctrlKey || e.metaKey || e.shiftKey || element.attr('target')) ) { var transition = $timeout(function() { // HERE we call $state.go inside of ui-sref $state.go(ref.state, params, options); });ui-sref最后調用的還是$state.go()方法
3、什么時候使用$watch
angular會為我們自動執行$watch,當指令中有獨立作用域,或者在異步函數中,改變的數據不在angular的執行上下文,就需要手動調用$apply 來觸發$digest去執行$watch
4、$scope 和$rootscope的區別是,$rootscope是$scope 的祖宗作用域