angular之中,$scope $rootScope $watch $state 是什么?


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 的祖宗作用域


最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容