[AngularJS面面观] 6. scope继承 - 基于原型继承的树形体系以及s
写过Angular应用的同学们或多或少都会注意到Angular框架在幕后会根据应用结构创建很多个scope,这些scope也许是继承自它的父节点的scope,也可能是隔离scope(Isolated Scope)。但是它们最终的父节点都是 现在我们就来探究一下scope的树形继承结构。具体分为一下几个话题: 本节主要聚焦于第一个和第二个话题。 scope继承的根基 - JavaScript原型继承不要以为Angular为了实现scope的继承玩出了什么新花样,在底层实现上,它还是依赖于JavaScript本身采用的原型继承(Prototypal Inheritance)。因此在学习Angular中的scope继承机制前,花时间了解一下JavaScript原型继承是十分必要的。 这里并不打算花太多的篇幅来阐述JavaScript原型继承是什么,有兴趣的同学可以移步这里学习一下相关概念。 当然本文还是会结合源代码来说说原型继承到底是怎么一回事。 function Scope() {
this.$id = nextUid();
// 更多的属性定义在这里
}
Scope.prototype = {
constructor: Scope,$new: function(isolate,parent) { /*方法定义*/ },$watch: function(watchExp,listener,objectEquality,prettyPrintExpression) {/*方法定义*/}
// 更多的scope方法
}
可以看到,除了scope类型的属性都定义在了Scope function中,它的方法全部都定义了 这只是一层原型继承,而所谓的原型继承链则是好多个prototype的链式关联。比如 对原型继承有了最基本的了解后,我们来看看scope的生命周期。谈到生命周期,就没法离开创建和销毁。对于scope而言,创建和销毁分别对应着 首先来看看创建: $new: function(isolate,parent) {
var child;
parent = parent || this;
if (isolate) {
child = new Scope();
child.$root = this.$root;
} else {
if (!this.$$ChildScope) {
this.$$ChildScope = createChildScopeClass(this);
}
child = new this.$$ChildScope();
}
child.$parent = parent;
child.$$prevSibling = parent.$$childTail;
if (parent.$$childHead) {
parent.$$childTail.$$nextSibling = child;
parent.$$childTail = child;
} else {
parent.$$childHead = parent.$$childTail = child;
}
if (isolate || parent != this) child.$on('$destroy',destroyChildScope);
return child;
}
首先,在创建scope的过程中可以接受两个参数: 那么,首先我们来看看当这两个参数什么都不传会发生些什么。 然后会判断当前scope上是否存在 function createChildScopeClass(parent) {
function ChildScope() {
this.$$watchers = this.$$nextSibling =
this.$$childHead = this.$$childTail = null;
this.$$listeners = {};
this.$$listenerCount = {};
this.$$watchersCount = 0;
this.$id = nextUid();
this.$$ChildScope = null;
}
ChildScope.prototype = parent;
return ChildScope;
}
可见,在这里设置了子scope的原型继承关系: 而这里我们正好也顺便看看一个每个子scope会拥有哪些属于自己的属性: 因此,创建的子scope实际上就是上述 child.$parent = parent;
// 孩子的前一个兄弟节点为父亲的最后一个孩子
child.$$prevSibling = parent.$$childTail;
if (parent.$$childHead) { // 当父节点还存在另外的孩子节点时
parent.$$childTail.$$nextSibling = child;
parent.$$childTail = child;
} else { // 当父节点没有另外的孩子节点时
parent.$$childHead = parent.$$childTail = child;
}
最后,当子scope为隔离scope或者子scope的父亲不是当前scope时,需要显式地声明一个回调函数用于销毁事件: if (isolate || parent != this) child.$on('$destroy',destroyChildScope);
这是因为在上述两种情况下,原型继承并没有发生作用。原因是压根就没有对原型继承链进行设置,即没有调用: 而关于scope的 从上面的代码来看,scope的创建过程并不复杂。主要是设置好原型继承链并将新创建的scope和已经存在的scope树形继承结构进行关联。 那么scope的销毁过程又是如何进行的呢?废话不说,直接上源代码: $destroy: function() {
// 避免重复销毁
if (this.$$destroyed) return;
var parent = this.$parent;
this.$broadcast('$destroy');
this.$$destroyed = true;
if (this === $rootScope) {
// 当销毁的对象为根scope时,销毁整个应用
$browser.$$applicationDestroyed();
}
incrementWatchersCount(this,-this.$$watchersCount);
for (var eventName in this.$$listenerCount) {
decrementListenerCount(this,this.$$listenerCount[eventName],eventName);
}
// 对现有的树形继承结构进行调整,从树中删除当前正在被销毁的节点,等待垃圾回收
if (parent && parent.$$childHead == this) parent.$$childHead = this.$$nextSibling;
if (parent && parent.$$childTail == this) parent.$$childTail = this.$$prevSibling;
if (this.$$prevSibling) this.$$prevSibling.$$nextSibling = this.$$nextSibling;
if (this.$$nextSibling) this.$$nextSibling.$$prevSibling = this.$$prevSibling;
// 无效化scope上的所有方法以及destroy方法本身
this.$destroy = this.$digest = this.$apply = this.$evalAsync = this.$applyAsync = noop;
this.$on = this.$watch = this.$watchGroup = function() { return noop; };
this.$$listeners = {};
this.$$nextSibling = null;
cleanUpScope(this);
}
上述代码主要做了几件事: 关于第一点,在后续专门讨论事件机制的文章中再进行讨论。 self.$$applicationDestroyed = function() {
jqLite(window).off('hashchange popstate',cacheStateAndFireUrlChange);
};
至于调整watchers以及listeners的计数信息,其实也很直观: function incrementWatchersCount(current,count) {
do {
current.$$watchersCount += count;
} while ((current = current.$parent));
}
function decrementListenerCount(current,count,name) {
do {
current.$$listenerCount[name] -= count;
if (current.$$listenerCount[name] === 0) {
delete current.$$listenerCount[name];
}
} while ((current = current.$parent));
}
需要注意的一点是:除了需要对当前正被销毁的scope的计数信息进行维护为,还需要维护它所有的父亲scope的计数信息。这一点从上面两个函数的while循环中可见一斑。 调整继承树形结构,更像是对链表这种数据结构的基本功练习。不明白的话,画个图仔细体会一下应该没有什么难的。 最后的两点,对被销毁scope上各种方法设置为noop,同时也将被销毁scope的各种关联关系抹去,目的都是防止误操作。 至此,scope生命周期中最重要的创建和销毁就介绍完毕了。在阅读源代码前,我觉得Angular的处理肯定用了什么黑魔法,但是在阅读后,觉得还是基本功更重要。再复杂的程序,背后的原理也许还是那么几个最根本的东西。 (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |