angular scope创建、继承和销毁以及scope生命周期(源码分析)

在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的事件机制和源码分析,好了,欢乐的时光总是过得特别快,又到时候和大家讲拜拜!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值