1.背景介紹
1、AngularJS Scope(作用域)
Scope(作用域)是應(yīng)用在 HTML (視圖)和 JavaScript (控制器)之間的紐帶柬赐。Scope是一個(gè)對(duì)象亡问,有可用的方法和屬性。Scope可應(yīng)用在視圖和控制器上肛宋。$scope的使用貫穿整個(gè) Angular App應(yīng)用,它與數(shù)據(jù)模型相關(guān)聯(lián),同時(shí)也是表達(dá)式執(zhí)行的上下文.有了 $scope就在視圖和控制器之間建立了一個(gè)通道,基于作用域視圖在修改數(shù)據(jù)時(shí)會(huì)立刻更新 $scope,同樣的 $scope發(fā)生改變時(shí)也會(huì)立刻重新渲染視圖.
2州藕、根作用域 rootScope
所有的應(yīng)用都有一個(gè) $rootScope,它可以作用在 ng-app指令包含的所有 HTML元素中酝陈。$rootScope可作用于整個(gè)應(yīng)用中床玻。是各個(gè) controller中 scope的橋梁。用 rootscope定義的值沉帮,可以在各個(gè) controller中使用
2.知識(shí)剖析
? ? ? 1锈死、$scope
$scope是一個(gè)把view(一個(gè)DOM元素)連結(jié)到controller上的對(duì)象。在我們的MVC結(jié)構(gòu)里穆壕,這個(gè) $scope將成為model待牵,它提供一個(gè)綁定到DOM元素(以及其子元素)上的excecution context。
$scope實(shí)際上就是一個(gè)JavaScript對(duì)象喇勋,controller和view都可以訪問(wèn)它缨该,所以我們可以利用它在兩者間傳遞信息。在這個(gè) $scope對(duì)象里川背,我們既可以存儲(chǔ)數(shù)據(jù)贰拿,又可以存儲(chǔ)將要運(yùn)行在view上的函數(shù)。每一個(gè)Angular應(yīng)用都會(huì)有一個(gè)$rootScope熄云。這個(gè)$rootScope 是最頂級(jí)的scope膨更,它對(duì)應(yīng)著含有ng-app 指令屬性的那個(gè)DOM元素。如果頁(yè)面上沒(méi)有明確設(shè)定$scope 缴允,Angular 就會(huì)把數(shù)據(jù)和函數(shù)都綁定到這里荚守。
Angular應(yīng)用啟動(dòng)并生成視圖時(shí),會(huì)將根 ng-app元素與 $rootScope進(jìn)行綁定.$rootScope是所有 $scope的最上層對(duì)象,可以理解為一個(gè) Angular應(yīng)用中得全局作用域?qū)ο?所以不應(yīng)該附加太多邏輯或者變量給$rootScope,和污染 Javascript全局作用域是一樣的道理.
$scope的作用
$scope對(duì)象在 Angular中充當(dāng)數(shù)據(jù)模型的作用,也就是一般 MVC框架中 Model得角色.但又不完全與通常意義上的數(shù)據(jù)模型一樣,因?yàn)?$scope并不處理和操作數(shù)據(jù),它只是建立了視圖和 HTML之間的橋梁,讓視圖和 Controller之間可以友好的通訊。
它有如下作用和功能:
提供了觀察者可以監(jiān)聽數(shù)據(jù)模型的變化
可以將數(shù)據(jù)模型的變化通知給整個(gè) App
可以進(jìn)行嵌套,隔離業(yè)務(wù)功能和數(shù)據(jù)
給表達(dá)式提供上下文執(zhí)行環(huán)境
在 Javascript中創(chuàng)建一個(gè)新的執(zhí)行上下文,實(shí)際就是用函數(shù)創(chuàng)建了一個(gè)新的本地上下文,
在 Angular中當(dāng)為子 DOM元素創(chuàng)建新的作用域時(shí),其實(shí)就是為子 DOM元素創(chuàng)建了一個(gè)新的執(zhí)行上下文.
$scope的生命周期有4個(gè)階段:
1.創(chuàng)建
控制器或者指令創(chuàng)建時(shí), Angular會(huì)使用 $injector創(chuàng)建一個(gè)新的作用域,然后在控制器或指令運(yùn)行時(shí),將作用域傳遞進(jìn)去.
2.鏈接
Angular啟動(dòng)后會(huì)將所有 $scope對(duì)象附加或者說(shuō)鏈接到視圖上,所有創(chuàng)建 $scope對(duì)象的函數(shù)也會(huì)被附加到視圖上.
這些作用域?qū)?huì)注冊(cè)當(dāng) Angular上下文發(fā)生變化時(shí)需要運(yùn)行的函數(shù).也就是 $watch函數(shù), Angular通過(guò)這些函數(shù)或者何時(shí)開始事件循環(huán).
3.更新
一旦事件循環(huán)開始運(yùn)行,就會(huì)開始執(zhí)行自己的臟值檢測(cè).一旦檢測(cè)到變化,就會(huì)觸發(fā) $scope上指定的回調(diào)函數(shù)
4.銷毀
通常來(lái)講如果一個(gè) $scope在視圖中不再需要, Angular會(huì)自己清理它.
ng-controller指令給所在的DOM元素創(chuàng)建了一個(gè)新的$scope對(duì)象练般,并將這個(gè)$scope對(duì)象包含進(jìn)外層DOM元素的$scope對(duì)象里健蕊。
在ng-app里,這個(gè)外層DOM元素的$scope對(duì)象踢俄,就是$rootScope對(duì)象缩功。這個(gè)scope鏈?zhǔn)沁@樣的:
所有scope都遵循原型繼承(prototypal inheritance),這意味著它們都能訪問(wèn)父scope們都办。對(duì)任何屬性和方法嫡锌,如果AngularJS在當(dāng)前scope上找不到,就會(huì)到父scope上去找琳钉,如果在父scope上也沒(méi)找到势木,就會(huì)繼續(xù)向上回溯,一直到$rootScope上歌懒。唯一的例外:有些指令屬性可以選擇性地創(chuàng)建一個(gè)獨(dú)立的scope啦桌,讓這個(gè)scope不繼承它的父scope們。
3、$watch:
? ? ? ? angularjs核心之一是雙向綁定甫男,那么這個(gè)雙向綁定是如何實(shí)現(xiàn)的呢且改?? 當(dāng)我們?cè)趧?chuàng)建出scope下的一個(gè)新屬性的時(shí)候,ng就會(huì)主動(dòng)為我們新屬性注冊(cè)$watch這個(gè)方法板驳,$watch用來(lái)監(jiān)聽的數(shù)據(jù)變化又跛,當(dāng)數(shù)據(jù)變化之后,就立即把view和scope上數(shù)據(jù)同步若治。AngularJS就能夠自動(dòng)注冊(cè)并監(jiān)聽變量的改變慨蓝。AngularJS會(huì)首先將在{{ }}中聲明的表達(dá)式編譯成函數(shù)并調(diào)用$watch方法。
?$watch是一個(gè)scope函數(shù)端幼,用于監(jiān)聽模型變化
?$watch(watchExpression, listener, objectEquality){ ... };
?watchExpression:$watch方法的第一個(gè)參數(shù)是一個(gè)函數(shù)礼烈,它通常被稱為watch函數(shù),它的返回值聲明需要監(jiān)聽的變量婆跑;
?listener:第二個(gè)參數(shù)是listener此熬,在變量發(fā)生改變的時(shí)候會(huì)被調(diào)用。和傳統(tǒng)的事件注冊(cè)和監(jiān)聽沒(méi)有什么本質(zhì)上的差別洽蛀,差別僅在于AngularJS能夠自動(dòng)注冊(cè)絕大多數(shù)的change事件并進(jìn)行監(jiān)聽摹迷,只要按照AngularJS要求的語(yǔ)法來(lái)寫HTML中的表達(dá)式代碼,即{{ }}郊供。 $watch方法為當(dāng)前scope注冊(cè)了一個(gè)watcher峡碉,這個(gè)watcher會(huì)被保存到一個(gè)scope內(nèi)部維護(hù)的數(shù)組中,即是$$watchers驮审。 watcher的主要目的是對(duì)scope上的某個(gè)屬性進(jìn)行監(jiān)控
? ? objectEquality:是否深度監(jiān)聽鲫寄,如果設(shè)置為true,它告訴Angular檢查所監(jiān)控的對(duì)象中每一個(gè)屬性的變化.? ?當(dāng)瀏覽器接收到可以被angular context處理的事件時(shí),$digest循環(huán)就會(huì)觸發(fā)疯淫。這個(gè)循環(huán)是由兩個(gè)更小的循環(huán)組合起來(lái)的地来。? ? 一個(gè)處理evalAsync隊(duì)列(這個(gè)沒(méi)有探究),另一個(gè)處理$watch隊(duì)列熙掺。$digest將會(huì)遍歷我們的$watch隊(duì)列未斑。如果有至少一個(gè)更新過(guò), 這個(gè)循環(huán)就會(huì)再次觸發(fā)币绩,直到所有的$watch都沒(méi)有變化蜡秽。這樣就能夠保證每個(gè)model都已經(jīng)不會(huì)再變化。 如果循環(huán)超過(guò)10次的話缆镣,它將會(huì)拋出一個(gè)異常芽突,防止無(wú)限循環(huán)。每次當(dāng)$digest循環(huán)結(jié)束時(shí)董瞻,DOM相應(yīng)地變化寞蚌。
? ? 例如我們按下按鈕觸發(fā)ng-click事件:
? 1、瀏覽器接收到一個(gè)事件,進(jìn)入angular context挟秤。
?2壹哺、 $digest循環(huán)開始執(zhí)行,查詢每個(gè)$watch是否變化煞聪。
? 3斗躏、 由于監(jiān)視$scope.name的$watch報(bào)告了變化逝慧,它會(huì)強(qiáng)制再執(zhí)行一次$digest循環(huán)昔脯。
? 4、 新的$digest循環(huán)沒(méi)有檢測(cè)到變化笛臣。
? ?5云稚、瀏覽器拿回控制權(quán),更新與$scope.name新值相應(yīng)部分的DOM沈堡。
? ? 6静陈、這里重要的是每一個(gè)進(jìn)入angular context的事件都會(huì)執(zhí)行一個(gè)$digest循環(huán),也就是說(shuō)每次我們輸入一個(gè)字母循環(huán)都會(huì)檢查整個(gè)頁(yè)面的所有$watch诞丽。? Angular會(huì)為我們自動(dòng)調(diào)用$apply鲸拥!因此當(dāng)點(diǎn)擊帶有ng-click的元素時(shí),事件就會(huì)被封裝到一個(gè)$apply調(diào)用僧免。? 比如有一個(gè)ng-model="foo"的輸入框刑赶,然后敲一個(gè)f,事件就會(huì)這樣調(diào)用$apply("foo = 'f';")懂衩,觸發(fā)$digest循環(huán)撞叨。
4、$state
? ? $state是ui-rooter的一項(xiàng)服務(wù)負(fù)責(zé)表示狀態(tài)以及它們之間的轉(zhuǎn)換浊洞。它還提供了接口來(lái)詢問(wèn)當(dāng)前狀態(tài)
常用的方法有:
$state.go(to, params, options) :轉(zhuǎn)換到新狀態(tài)的方便方法
$state.includes(stateOrName, params, options) :確定當(dāng)前活動(dòng)狀態(tài)是否等于或是狀態(tài)狀態(tài)子的方法牵敷。返回布爾值
$state.params: 返回狀態(tài)參數(shù)的對(duì)象$stateParams
$stateParams是一個(gè)對(duì)象,包含 url中每個(gè)參數(shù)的鍵/值法希。$stateParams可以為控制器或者服務(wù)提供 url的各個(gè)部分枷餐。
? ? 注意:$stateParams必須與一個(gè)控制器相關(guān),并且$stateParams中的“鍵/值”也必須事先在那個(gè)控制器的url屬性中有定義苫亦。
3.常見問(wèn)題
1毛肋、 如何自定義$watch?
2、 什么時(shí)候需要我們?nèi)フ{(diào)用$watch?
4.解決方案
1著觉、自定義自己的watches:
? ? angular.module("myApp",[]).controller('MainCtrl', function($scope) {$scope.name = "hello";$scope.updated = -1;$scope.$watch('name', function() {$scope.updated++;});});
//創(chuàng)造一個(gè)新的$watch的方法落君。第一個(gè)參數(shù)是一個(gè)字符串或者函數(shù)萍肆,在這里是只是一個(gè)字符串,就是我們要監(jiān)視的變量的名字,
//第二個(gè)參數(shù)是當(dāng)$watch說(shuō)我監(jiān)視的表達(dá)式發(fā)生變化后要執(zhí)行的嗓违。當(dāng)controller執(zhí)行到這個(gè)$watch時(shí)人乓,它會(huì)立即執(zhí)行一次
2、 取消 $watch :
$watch會(huì)影響性能問(wèn)題,特別是在移動(dòng)設(shè)備上油啤,在不需要時(shí)應(yīng)該清除
$watch函數(shù)本身返回一個(gè)函數(shù),所以,當(dāng)$watch不再需要的時(shí)候蟀苛,我們只需調(diào)用返回的函數(shù)即可:
.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益咬、 什么時(shí)候需要我們?nèi)フ{(diào)用$watch?
被調(diào)用的事件沒(méi)有進(jìn)入angular context,$digest循環(huán)永遠(yuǎn)沒(méi)有執(zhí)行帜平。這種情況一般出現(xiàn)在指令的隔離作用域中
幽告,也會(huì)出現(xiàn)在異步執(zhí)行的函數(shù)體中。調(diào)用$watch需要通過(guò)$apply裆甩。
6.擴(kuò)展思考
指令中的scope三個(gè)值有什么用冗锁?
? ?false 共享作用域
? ? ? true 創(chuàng)建自己的作用域,并繼承父作用域
? ? ? {}創(chuàng)建隔離作用域
7.參考文獻(xiàn)
參考一:? Angular.js中使用$watch監(jiān)聽模型變化 http://yuankeqiang.lofter.com/post/8de51_1454f93
參考二:關(guān)于$watch應(yīng)用的一些小技巧?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服務(wù)原版詳解? https://www.cnblogs.com/koleyang/p/4576419.html
問(wèn)題:
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 的區(qū)別ui-sref 一般使用在...茉帅;消息中心$state.go('someState')一般使用在 controller里面叨叙;.controller('firstCtrl', function($scope, $state) { $state.go('login'); });這兩個(gè)本質(zhì)上是一樣的東西,我們看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最后調(diào)用的還是$state.go()方法
3堪澎、什么時(shí)候使用$watch
angular會(huì)為我們自動(dòng)執(zhí)行$watch擂错,當(dāng)指令中有獨(dú)立作用域,或者在異步函數(shù)中全封,改變的數(shù)據(jù)不在angular的執(zhí)行上下文马昙,就需要手動(dòng)調(diào)用$apply 來(lái)觸發(fā)$digest去執(zhí)行$watch
4、$scope 和$rootscope的區(qū)別是刹悴,$rootscope是$scope 的祖宗作用域