深入理解AngularJs-scope(一)中,我們對AngularJs的臟檢測及其觸發、異步任務隊列進行了學習。緊接上一篇文章 深入理解AngularJs-scope(一),我們來看看scope對以下兩個特性的實現。
- scope的繼承機制和 isolated scope;
- 依賴於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開發的朋友。如有錯誤,請不吝指出~!謝謝~