翻譯:angularJS Scope綜述

筆記類文章

angularJS Scope綜述

于2017年1月14日 翻譯自angularjs 官網(wǎng)開發(fā)者指南

每一個應用都僅有一個根域,其他所有域都是它的子孫,scopes將model和view分離開來,并通過一種機制監(jiān)控model變化,它也提供事件的發(fā)散/廣播并且提供設施。

一個根域($rootScope),通過$injector加載根域的名字,可以被重新獲取。子域(childScopes)通過$new()方法來創(chuàng)建。(大部分的域scope是在HTML模版編譯完成時自動創(chuàng)建的)

英文原文

什么是Scope

scope是一個應用模型對象,它是一個表達式的執(zhí)行上下文。它被用于那些模仿DOM結構的分層結構的應用里。它可以監(jiān)控表達式和傳播事件。

Scope特性

  • Scopes提供($watch)來觀察model的變化
  • Scopes提供($apply)來溝通視圖(view)到angular領域外的系統(tǒng),以便傳播任何model的變化,(controllers, services, angular event handlers)
  • 可以通過嵌套scope來限制其對應用程序組件屬性的使用權。嵌套域是child scopesisolate scopes(獨立子域,directive創(chuàng)建的scope)這種子域。child scope會從父域繼承屬性,isolate scope則不會
  • Scopes會針對被求值的表達式提供上下文,eg:{{username}}表達式是無意義的,除非在一個特定的scope中定義了username

數(shù)據(jù)模型的scope

  • scope是應用在控制器和視圖之間的粘合劑。在模版的linking階段,directives指令在scope上建立$watch來監(jiān)控屬性的變化(如果變化,通知directive),并允許指令為DOM重新渲染更新后的數(shù)據(jù)。
  • 所有的控制器和指令都與scope有關,而不是彼此有關,這種布局將控制器與指令很好的分離開來,就像控制器與DOM一樣。讓控制器變的不可知是很重要的,這大大改善了應用調試時的情況。
  • 在邏輯上,渲染dom中如{{greeting}}過程是:
    • 遍歷scope關聯(lián)的模版中被定義{{greeting}}的DOM節(jié)點。
    • 依據(jù)正在遍歷的scope重新計算表達式,然后重新設置結果。
  • 可以把scope和其上屬性想象為用于渲染視圖的data。scope僅僅是所有與視圖有關的實物的實際來源(source-of-truth)。
  • 在一個視圖的可測試點(testability point of view?可能是angular的測試模塊的東西ngMock),分離視圖和控制器是不可能的。因為它允許我們檢測行為而不用分心于渲染細節(jié)。

scope 層級

  • 每個angular程序都只有一個root scope,但有很多的孩子域。
  • 應用可以有多個域,因為有一些指令會創(chuàng)建新的子域,新的子域在創(chuàng)建完成后,被當作父域的孩子插入到父域中。這種方式,將在與Dom互相依賴的地方,創(chuàng)建了一顆與dom平行的樹狀結構。
  • 來看一個例子:
//html
<div class="show-scope-demo">
  <div ng-controller="GreetController">
    Hello {{name}}!
  </div>
  <div ng-controller="ListController">
    <ol>
      <li ng-repeat="name in names">{{name}} from {{department}}</li>
    </ol>
  </div>
</div>
//javascript
angular.module('scopeExample', [])
.controller('GreetController', ['$scope', '$rootScope', function($scope, $rootScope) {
  $scope.name = 'World';
  $rootScope.department = 'Angular';
}])
.controller('ListController', ['$scope', function($scope) {
  $scope.names = ['Igor', 'Misko', 'Vojta'];
}]);

域的劃分情況:

.1484355221750.png
  • 應當注意:angular自動添加ng-scope類名到那些被附加了scope的元素上。
  • 子域是必要的,因為重復的對如{{name}}這樣的表達式求值,這時,根據(jù)表達式求值時的子域不同,得到不同的結果。類似的,對于{{department}}的求值,他繼承自根域。

從DOM retrieving(重新檢索?) scopes

  • scope以$scope這樣的data屬性附加到DOM上,處于檢錯的目的,他們是可以被檢索的。(這不是說,將必須在程序中以這種方式重新檢索scope)
  • ng-app指令定義了root scope將被附加到DOM的哪個位置。對于將ng-app放在<html>標簽上,如果一個頁面只有一部分需要被angular控制,那么放在其他的位置會更好。
  • 在debugger中檢查scope
    • Right click on the element of interest in your browser and select 'inspect element'. You should see the browser debugger with the element you clicked on highlighted
    • debugger允許在控制臺中以$0變量來使用當前選擇的元素
    • 通過angular.element($0).scope()來在控制臺重新檢索元素關聯(lián)的scope,或者輸入$scope也可以。

域事件的傳播

  • 在同樣的fashion中,scope可以向dom事件傳播事件,事件可以broadcasted(廣播)給孩子域或emit(發(fā)散)給父域。
  • 使用$emit('事件名')來發(fā)散給父域,$broadcast('事件名')廣播給子域。
  • 在接受事件的域使用$on('對應的事件名')來接受(注意,兩邊的事件名必須一致)
  • eg:
//html
<div ng-controller="EventController">
  Root scope <tt>MyEvent</tt> count: {{count}}
  <ul>
    <li ng-repeat="i in [1]" ng-controller="EventController">
      <button ng-click="$emit('MyEvent')">$emit('MyEvent')</button>
      <button ng-click="$broadcast('MyEvent')">$broadcast('MyEvent')</button>
      <br>
      Middle scope <tt>MyEvent</tt> count: {{count}}
      <ul>
        <li ng-repeat="item in [1, 2]" ng-controller="EventController">
          Leaf scope <tt>MyEvent</tt> count: {{count}}
        </li>
      </ul>
    </li>
  </ul>
</div>
angular.module('eventExample', [])
.controller('EventController', ['$scope', function($scope) {
  $scope.count = 0;
  $scope.$on('MyEvent', function() {
    $scope.count++;
  });
}]);

scope生命周期

  • 瀏覽器普通流在一個事件執(zhí)行相應js回調函數(shù)時才接受它。一旦回調函數(shù)執(zhí)行完畢,瀏覽器會重新渲染dom,并返回到等待事件的狀態(tài)。
  • 當瀏覽器調用angular執(zhí)行上下文之外的js代碼時,這意味著angular不會意識到model的修改。為了正確的處理model的改變,需要使用$apply方法,將執(zhí)行過程加入到angular的執(zhí)行上下文。只有在$spply中的model處理,才將被angular正確的解釋。比如,一個指令監(jiān)聽dom事件,他必須在$apply方法中對表達式求值。
  • 在對表達式求值后,$apply方法將執(zhí)行$digest.在$digest階段,scope檢查()所有的$watch表達式,并且將他們與以前的值進行比較。這個就是臟檢查,它是異步的。這意味著,賦值將(eg:$scope.username = "angular")不會立刻通知$watch,而是延遲到$digest階段。這種延遲是必要的,因為,它會在一個$watch中合并大量的model更新,同時,也保證了,在這個$watch執(zhí)行的過程中,沒有別的$watch正在運行。如果在某個$watch中又改變了model的值,他會強制觸發(fā)額外的$digest循環(huán)
    • Creation
      root scope在應用$injector的引導程序(bootstrap)的過程中被創(chuàng)建,在連接模版(linking)時,一些指令會創(chuàng)建子域。
    • Watcher registration (注冊觀察者)
      在模版連接(linking)時,指令(directive)將在域(scope)上注冊觀察者(watches),這些觀察者將被用于傳播model的值到DOM
    • model mutation(變化)
      因為變化需要被正確的觀察到,應確保他們僅僅在scope.$apply()內部。angular APIs,隱含的做了這樣的處理,所以,在controllers中進行同步的任務時;或者使用$http,$timeout,$interval等服務進行異步任務時,不需要額外的調用$apply.
    • mutation observation(變化觀察)
      所有的$apply的末尾,angular會在root scope上執(zhí)行一個$digest循環(huán),這個循環(huán)會波及到所有的孩子域。在$digest期間,所有被$watch的表達式或者函數(shù),會被檢查model是否變化,一旦發(fā)現(xiàn)變化,$watch的監(jiān)聽者就被調用。
    • scope destruction(域的消亡)
      當某個子域(child scope)不再不需要了,那么這個子域的創(chuàng)造者有義務通過scope.$destroy()api來銷毀這個子域。這將停止再向這個子域傳播$digest調用,并且允許,被這個子域model占用的內存被garbage collector回收。

scopes 和 directives(域和指令)

在編譯階段(comlilation),compiler編譯器會針對DOM模版來匹配directives指令,指令通常分為一或兩種類別:

  • 觀察者型指令(observing directives),比如兩個花括號的表達式{{expression}},它會通過$watch()方法注冊監(jiān)聽者。這種類型的指令,只要表達式變化(值的變化)就會被通知,所以它可以更新視圖數(shù)據(jù)。
  • 監(jiān)聽者型指令(listener directives),比如ng-click,在DOM上注冊一個監(jiān)聽者,當這個dom監(jiān)聽者激發(fā)時,這種指令會執(zhí)行與其相關聯(lián)的表達式并且通過$apply()方法來更新視圖(view)
    當接收一個外部的事件時,它關聯(lián)的表達式必須通過$apply()方法應用到scope,以保證所有的監(jiān)聽者正確的更新。

創(chuàng)造域的指令

在大多數(shù)情況下,指令和域將繼承而不是創(chuàng)造,一個新的scope實例。然而,有的指令,比如ng-controller,ng-repeat會創(chuàng)造一個新的子域并且將這個子域關聯(lián)到相應的DOM元素上,可以在任何DOM元素上,通過angular.element(aDomElement).scope()方法來檢索scope。

控制器和域

域和控制器在以下情況將會互相影響

  • 控制器通過scope暴露控制器的方法
  • 控制器定義可以影響model(scope上的屬性)的方法時。
  • 控制器可能會在model上注冊watches,這些watches會在控制器行為執(zhí)行后,立刻執(zhí)行。

scope watch性能注意事項

臟檢查scope屬性的變化,在angularz中是一個公共的操作,所以臟檢查函數(shù)需要很高效。需要注意,在臟檢查函數(shù)中,避免過多的DOM操作,因為,DOM操作比在js對象上操作屬性慢的多的。

Scope$watchdepths 深度域$watch

如圖:

.1484375972331.png

完成臟檢查有三種策略,引用(by reference),數(shù)據(jù)集合(by collection items),值(by value)。這些策略的不通點在于他們察覺的變化的種類,和他們各自的工作特性(運行方式)。

  • 通過引用監(jiān)聽(scope.$watch(watchExpression, listener)),當監(jiān)聽表達式轉變?yōu)橐粋€新的值并返回完整的值時,可以察覺到變化。如果返回值是一個array或者object,那么他們內部的變化將不會被察覺到,這是最有效率的模式
  • 觀察數(shù)據(jù)集合 (scope.$watchCollection(watchExpression, listener)),會察覺到在數(shù)組或對象內部的變化:當item被添加,移除或者重新排序。它是一個淺觀測——即,不會觀測到嵌套結構下的數(shù)據(jù)集合(就是對象內嵌對象查不到)。這種策略比上一種代價高,因為需要維持一個數(shù)據(jù)集合的副本。但這個策略也會企圖使復制請求的總數(shù)盡量的少。
  • value監(jiān)聽($scope.$watch(watchExpression, listener, true)),察覺到所有的變化,(各種嵌套數(shù)據(jù)結構之類的,都會檢查),這是最全面的檢查變化策略,但是也是代價最大的,在每一個digest都必須完全遍歷嵌套的數(shù)據(jù)結構,并且在內存中維持一個完全拷貝的的副本

集成瀏覽器事件循環(huán)

.1484379812975.png

上圖和接下來的例子將會描述,angular是如何集成瀏覽器的事件循環(huán)

  • 瀏覽器的事件循環(huán)等待一個事件的到達。(用戶交互事件,timer事件,網(wǎng)絡任務事件)
  • 某事件的回調函數(shù)在js上下文中得到執(zhí)行,這個回調函數(shù)可以修改dom結構。
  • 一旦回調函數(shù)得到執(zhí)行,瀏覽器就離開javascript上下文,并且根據(jù)dom的變化重繪視圖(view)。

angular通過提供它自己的事件執(zhí)行循環(huán),修改了普通javascript流,它將javascript分成了普通上下文和angular執(zhí)行上下文,只有應用在angular執(zhí)行上下文才能具有angular的數(shù)據(jù)綁定,異常處理,屬性監(jiān)控,等等。當然,也可以使用$apply()方法從javascript進入angular執(zhí)行環(huán)境。要記住,在很多地方(controllers,services)$apply已經(jīng)被那些正在處理事件的指令調用過了。僅僅在執(zhí)行自定義事件回調函數(shù)或者需要運行第三方庫的回調函數(shù)的時候使用$apply()。

  • 通過調用scope.$apply(stimulusfn)來進入angular執(zhí)行上下文,stimulusfn是你希望在angular執(zhí)行上下文中運行的任務
  • angular運行stimulusFn()來修改應用的狀態(tài)。
  • angular進入$digest循環(huán),這個循環(huán)是建立在兩個更小的循環(huán)之上的:分別用于處理$evalAsync隊列和$watch列表。$digest將會持續(xù)迭代,直到model穩(wěn)定——即$evalAsync隊列為空且$watch列表不檢查任何變化。
  • $evalAsync隊列常用于安排那些需要在當前堆棧外,但在瀏覽器更新視圖前發(fā)生的任務,通常使用setTimeout(0)實現(xiàn),但是,setTimeout(0)方法很遲鈍并且可能導致視圖閃爍,因為瀏覽器會在任何一個事件后渲染視圖。
  • $watch是一個列表,保存著由于最近一次迭代而發(fā)生改變的表達式們。如果一個改變被檢查到,那么就調用那些以新值更新DOM的$watch函數(shù)。
  • 一旦angular的$digest循環(huán)完成,執(zhí)行上下文將離開angular和javascript。接下來瀏覽器重構DOM來反映出所有的變化。

下面解釋一下hello word例子中,當用戶輸入文本時是如何實現(xiàn)數(shù)據(jù)綁定的效果的。

  • 在編譯階段
    • ng-modelinput directive<input>建立一個keydown監(jiān)聽者
    • interpolation(插入者?處理器?)建立一個$watch以便name變化時通知它。
  • 在運行階段
    • 按下某個鍵盤鍵x,會導致瀏覽器在<input>控制器上發(fā)散一個keydown事件
    • input指令捕獲到輸入值的變化,并調用$apply("name = 'x';")方法來在angular執(zhí)行上下文中更新應用model
    • angular將name = "x"應用到model
    • $digest循環(huán)開始
    • $watch列表檢查到在name屬性上有一個變化,并且通知interpolationinterpolation輪流更新DOM
    • angular退出那些,連帶javascript執(zhí)行環(huán)境一起輪流退出keydown事件的,執(zhí)行上下文。
    • 瀏覽器以新值重新渲染view

繼承性

一個域可以從父域繼承

在測試scope的相互作用時,為scope的實例添加一些額外的幫助函數(shù)是很有效的,這些函數(shù)在ngMock中有介紹

  • expect(exp).toEqual(value)
    計算exp并與后邊的value做比較
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,646評論 6 533
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,595評論 3 418
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 176,560評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,035評論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,814評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,224評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,301評論 3 442
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,444評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,988評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,804評論 3 355
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,998評論 1 370
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,544評論 5 360
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,237評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,665評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,927評論 1 287
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,706評論 3 393
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,993評論 2 374

推薦閱讀更多精彩內容