深入理解AngularJs-scope(二)


深入理解AngularJs-scope(一)中,我們對AngularJs的臟檢測及其觸發、異步任務隊列進行了學習。緊接上一篇文章 深入理解AngularJs-scope(一),我們來看看scope對以下兩個特性的實現。

  1. scope的繼承機制和 isolated scope;
  2. 依賴於scope的事件系統: $on, $broadcast, $emit;

scope的繼承機制

  在上一篇文章中,我們創建了scope類,並在scope.prototype上實現了臟檢測和異步任務相關的各個方法。

  現在,我們來看看AngularJs中,scope之間是如何通過繼承聯系在一起的,如何從parentScope上獲取properties。實際上,得益於javascript的原型繼承機制,要實現scope的繼承相當的簡單,代碼如下:

 1 Scope.prototype.$new = function(isolated, parent) {
 2     var child;
 3     parent = parent || this;
 4 
 5     if(isolated) {
 6         child = new Scope();
 7         child.$root = parent.$root;
 8         child.$$asyncQueue = parent.$$asyncQueue;
 9         child.$$postDigestQueue = parent.$$postDigestQueue;
10         child.$$applyAsyncQueue = parent.$$applyAsyncQueue;
11     } else {
12         var ChildScope = function() {};
13         ChildScope.prototype = this;
14         child = new ChildScope();
15     }
16             
17     parent.$$children.push(child);
18 
19     child.$$watchers = []; // shadow這個prop,使成為每個scope獨立擁有這個prop
20     child.$$listeners = {}; // shadow這個prop, 存儲包含自定義事件鍵值對的對象 
21     child.$$children = []; // shadow這個prop,使成為每個scope獨立擁有這個prop
22     child.$parent = parent; // 緩存parentScope, 以便讓scope上的其他method能夠使用它,比如$destroy
23 
24     return child;
25 };

  在我們使用AngularJs進行開發時,$new方法的調用無處不在。大部分情況下並不需要我們手動調用,只是指令自己做了創建scope的工作。

  $new方法有兩個參數:

    isolated-布爾值,表示新建的scope是不是 isolated scope(孤立的scope)。

    parent-支持傳入一個其他scope來作為 AngularJs scope機制中的parentScope。    

  AngularJs中的scope分為兩種,一種是普通scope,如上面代碼13行所示,普通scope的prototype指向parentScope, 故能夠通過原型鏈獲取到parentScope上的properties。

                  另一種是isolated(孤立) scope, isolated是通過 Scope構造函數創建,它的protorype是指向scope構造函數的,並不是parentScope,所以不能從原型鏈訪問parentScope的properties。

  代碼19行至22對新scope的$$watchers、$$listeners、$$children、$parent進行了初始化,因為這些屬性是每個scope實例自己擁有、自己維護的。

 

  scope的事件系統:訂閱/發布

  AngularJs 也為開發者提供了一套事件系統供開發者進行事件的綁定和觸發,基於 publish/subscribe 設計模式。其中包含3個核心方法:$on, $emit, $broadcast。

  $on: 在scope上綁定自定義事件,即向scope的$$listeners數組中插入listener回調函數。返回事件銷毀函數。

 1 Scope.prototype.$on = function(eventName, listener) {
 2     var listeners = this.$$listeners[eventName];
 3 
 4     if(!listeners) {
 5         this.$$listeners[eventName] = listeners = [];
 6     }
 7 
 8     listeners.push(listener);
 9 
10     return function(eventName) {
11         var index = listeners.indexOf(listener);
12                 
13         if(index >= 0) {
14             listeners[index] = null;
15         }
16     };
17 };

 

  $emit: 沿着scope -> parentScope 向上發射事件,執行對應的回調函數。

Scope.prototype.$emit = function(eventName) {
            var propagationStopped = false;
            var event = {
                name: eventName,
                targetScope: this,
                stopPropagation: function() {
                    propagationStopped = true;
                },
                preventDefault: function() {
                    event.defaultPrevented = true;
                }
            };

            //  把event和additionalArgs拼接成新數組,通過apply方法傳入listener, 使參數獲取方式正常
            var listenerArgs = [event].concat(_.tail(arguments));
            var scope = this;

            do {
                event.currentScope = scope;
                scope.$$fireEventOnScope(eventName, listenerArgs);
                scope = scope.$parent;  // 通過改變scope引用 實現向上傳播的關鍵代碼
            } while (scope && !propagationStopped);

            event.currentScope = null;

            return event;
        };

 

   $broadcast: 向下廣播事件,並觸發對應的回調函數。$broadcast有一點特殊,一旦開始向下廣播,就不能中斷。

 1         Scope.prototype.$broadcast = function(eventName) {
 2             var event = {
 3                 name: eventName,
 4                 targetScope: this,
 5                 preventDefault: function() {
 6                     event.defaultPrevented = true;
 7                 }
 8             };
 9 
10             //  把event和additionalArgs拼接成新數組,通過apply方法傳入listener, 使參數獲取方式正常
11             var listenerArgs = [event].concat(_.tail(arguments));
12 
13             this.$$everyScope(function(scope) {
14                 event.currentScope = scope;
15                 scope.$$fireEventOnScope(eventName, listenerArgs);
16                 return true;
17             });
18 
19             event.currentScope = null;
20 
21             return event;
22         };    

這兩篇用到的工具函數我都放在后面,由於事件系統的代碼比較簡單,就不再做過多說明。

工具方法1: $$fileEventOnScope

 1 /*  $emit 和 $broadcast中 提取出的 fire event listener函數
 2     angularjs源碼沒有這個方法,其中只是重復了這些代碼, 本書作者提出了重復代碼
 3  */
 4         Scope.prototype.$$fireEventOnScope = function(eventName, listenerArgs) {
 5 
 6             var listeners = this.$$listeners[eventName] || [];
 7             var i = 0;
 8 
 9             while(i < listeners.length) {
10                 if(listeners[i] === null) {
11                     listeners.splice(i, 1);
12                 } else {
13                     try {
14                         listeners[i].apply(null, listenerArgs);
15                     } catch(e) {
16                         console.error(e);
17                     }
18                     i++;
19                 }
20             }
21 
22             return event;
23         };

 

工具方法2: $$everyScope

 1 /* 為使$digest循環能夠遞歸child scope上的watchers的工具方法 
 2     這個方法還用於實現$broadcast
 3     */
 4 Scope.prototype.$$everyScope = function(fn) {
 5         if(fn(this)) {
 6             return this.$$children.every(function(child) {
 7                 return child.$$everyScope(fn);
 8             });
 9         } else {
10             return false;
11         }
12 };    

 

  總結:

  這兩篇文章提供了與scope相關的臟檢測,$watch, 異步任務,繼承機制,事件系統的代碼及一點補充分析。弄明白了這些機制是如何實現的,當你在開發工作中用到這些東西時,一定會多一份自信,多一份游刃有余。希望這兩篇文章能夠幫助到正在使用angular1.x開發的朋友。如有錯誤,請不吝指出~!謝謝~


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM