在js中有函数作用域和全局作用域(es5),angular也存在作用域,它是如何创建的呢?
1.scope的创建和继承
首先ng-app指令会查找作用范围,在它上面会有个根作用域($rootScope):
function Scope() {
this.$id = nextUid();// uid = 0 function nextUid() { return ++uid ;}
this.$$phase = this.$parent = this.$$watchers =
this.$$nextSibling = this.$$prevSibling =
this.$$childHead = this.$$childTail = null;
this.$root = this;
this.$$destroyed = false;
this.$$suspended = false;
this.$$listeners = {};
this.$$listenerCount = {};
this.$$watchersCount = 0;
this.$$isolateBindings = null;
}
var $rootScope = new Scope();
此时开始创建了根作用域,分析下那些变量的含义
1.$id: 创建一个作用域id就增1(初始id为1)。
2.$$phase: 这个是脏检查的一个阶段变量(如果不知道,可以看下之前的文章,点击这里)。
3.$parent: 父级作用域。
4.$$nextSibling: 上一个兄弟作用域。
5.$$prevSibling: 下一个兄弟作用域。
6.$$childHead: 第一个子作用域。
7.$$childTail: 最后个子作用域。
8.$$watchers: 此作用域下的监听对象(是一个数组来的)。
9.$$destroyed : 初始值为false,当摧毁的时候为true。
10.$$listeners: 此作用域下的注册的监听器事件。
11.$$listenerCount:此作用域下(包括子作用域)的注册监听器事件总数。
12.$$watchersCount:此作用域下(包括子作用域)注册的watchers对象对应的总数。
13.$root: 所有的作用域通过这个属性可以关联到$rootScope。
知道了这些属性,那么作用域是什么创建的呢(总不能一个标签一个作用域?)我们先说简单的继承作用域,那么angular哪些指令会创建子作用域且继承自父作用域:
1、 ng-repeat。
2、 ng-include。
3、 ng-switch。
4、 ng-controller。
5、 directive (scope: true)。
6、 directive(transclude: true)。
这些指令都是自己创建作用域,并且继承父作用域,这里我说的继承和js的原型继承是类似的(ChildScope.prototype = parent),如果不是很懂,可以点击这里。还有一个隔离作用域,我们在写指令过程中,应该经常用到“directive(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;
}
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;
this.$$suspended = false;
}
ChildScope.prototype = parent;
return ChildScope;
}
angular是通过指令和$new方法来创建作用域,我们来分析里面源码的:
首先有2个参数,一个是个布尔值,用于指定创建的作用域是否为一个隔离作用域,另外一个scope对象。传入的scope对象会被指定为当前正在创建的scope的父亲,那么我们如果什么都不传会怎么样,先判断是否已经创建了子scope,如果没有创建就通过createChildScopeClass构造函数new出子scope并继承父(ChildScope.prototype = parent),然后要把它的爸爸啊,儿子啊,兄弟姐妹,要整明白,要不然乱套了,这段代码比较好理解,不理解就画个图会很容易理解,最后,当子scope为隔离scope或者子scope的父亲不是当前scope时,声明一个回调函数用于销毁事件。这是因为在上述两种情况下,原型继承并没有发生作用。原因是压根就没有对原型继承链进行设置,即没有调用:ChildScope.prototype = parent。从上面的代码来看,scope的创建过程并不复杂。主要是设置好原型继承链并将新创建的scope和已经存在的scope树形继承结构进行关联。
2.scope的销毁过程
那么scope的销毁过程又是如何进行的呢?废话不说,直接上源代码:
$destroy: function () {
// 判断是否已销毁,避免重复销毁.
if (this.$$destroyed) return;
var parent = this.$parent;
//向子scope广播销毁事件
this.$broadcast('$destroy');
//默认值为false改为true
this.$$destroyed = true;
if (this === $rootScope) {
//当销毁的对象为根scope时,销毁整个应用
$browser.$$applicationDestroyed();
}
//销毁了之后把Watcher对象监听记数信息减少(包括父scope上的记数信息),
incrementWatchersCount(this, -this.$$watchersCount);
//销毁了之后把监听事件记数信息减少(包括父scope上的记数信息)
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上的方法全部销毁
this.$destroy = this.$digest = this.$apply = this.$evalAsync = this.$applyAsync = noop;
//销毁所有的回调事件
this.$on = this.$watch = this.$watchGroup = function () {
return noop;
};
this.$$listeners = {};
// Disconnect the next sibling to prevent `cleanUpScope` destroying those too
this.$$nextSibling = null;
//IE9中就会出现内存泄漏,写了个兼容IE9方法
cleanUpScope(this);
},
上面源代码我注释已经写得很清楚了,销毁的过程,其实是对当前正被销毁的scope的计数信息进行修正,还需要修正它所有的父scope的计数信息。这一点从上面两个函数的while循环中看出来的,然后把自己的父,兄弟姐妹,儿子整明白(要不然乱套了),最后对被销毁scope上各种方法设置为noop,同时也销毁scope的各种回调,目的都是防止误操作。scope生命周期中最重要的创建和销毁就说完了,下一篇文件我会讲下angular的事件机制和源码分析,好了,欢乐的时光总是过得特别快,又到时候和大家讲拜拜!!