两只小蚂蚁

  博客园 :: 首页 :: 博问 :: 闪存 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理 ::

 

  我们在这里不讨论Angular2和Angular4,因为其完全重写,其实已经不叫AngularJS了。

  AngularJS的缺陷:

    • 性能问题:通过检查脏值进行数据更新,当数据不断增加时,检查的效率就不断降低。页面加载速度也会变慢。
    • 落后于当前web发展理念(如组件式的开发)
    • 对手机端的支持不是太友好        

1. 执行时机

  angularJS定义是一个立即执行的匿名函数,那么其执行时机为引入angularJS的位置决定。

(function(window) {
    ...
})(window);

2. 源码结构

  基于1.6.9

  源码的上部分基本在定义方法,其主要执行开始在源码的最后,主要函数如下:  

bindJQuery();

publishExternalAPI(angular);

jqLite(function() {
    angularInit(window.document, bootstrap);
  });

  2.1 bindJQuery:

    尝试绑定jQuery对象,如果没有则采用内置的jqLite

  2.2 publishExternalAPI:

    •  绑定一些公共的方法到angular,如copy,bind等。
function publishExternalAPI(angular) {
    extend(angular, {      
      'bootstrap': bootstrap,
      'copy': copy,
      'extend': extend,      
      'bind': bind,
      ...      
    });
    angularModule = setupModuleLoader(window);
    angularModule('ng', ['ngLocale'], ['$provide',
    function ngModule($provide) {
        ...
    }
    ...
}
    •  setupModuleLoader:在window下面创建全局的angular对象,并且返回一个高阶函数,赋值给了angular.module属性,所以一般我们创建模块都是用angular.module方法。

  2.3 angularInit:

    找到带angular项目标识的元素,然后调用bootstrap方法。  

function angularInit(element, bootstrap) {
    var appElement,
        module,
        config = {};
    forEach(ngAttrPrefixes, function(prefix) {
      var name = prefix + 'app';  
      if (!appElement && element.hasAttribute && element.hasAttribute(name)) {
        appElement = element;
        module = element.getAttribute(name);
      }
    });
   ...
    if (appElement) {
      ...      
      bootstrap(appElement, module ? [module] : [], config);
    }
  }

    bootstrap函数主要功能:创建注入器和用注入器进行调用

function bootstrap(element, modules, config) {
    ...
    var injector = createInjector(modules, config.strictDi);
    injector.invoke(['$rootScope', '$rootElement', '$compile', '$injector',
       function bootstrapApply(scope, element, compile, injector) {
        scope.$apply(function() {
          element.data('$injector', injector);
          compile(element)(scope);
        });
      }]
    );
    ...
}

    createInjector:其内部代码复杂,DI及provider在此不做展开。在这里模块加载完成后,运行模块,生成实例。

    injector.invoke:生成编译实例,通过编译实例去编译项目起始页,编译的核心是生成指令对应的link函数,有点类似后端的编译,先词法分析,用lex,然后语法分析,用parse,最后链接,生成link函数。具体分析如下

  2.4 RootScope核心

    在publishExternalAPI函数中我们定义了Provider,这个provider实现了AngularJS的核心方法。

      $rootScope: $RootScopeProvider

    接下来看看该provider的源码,下一章我们将分析其核心实现细节。

function $RootScopeProvider() {
    var TTL = 10;
    this.digestTtl = function(value) {
     ...
    };
  
    this.$get = ['$exceptionHandler', '$parse', '$browser',
        function($exceptionHandler, $parse, $browser) {
  
      function Scope() {
         ...
      }  
  
      Scope.prototype = {
        constructor: Scope,
        $new: function,
        $watch: function,
        $watchGroup: function,
        $watchCollection: function,
        $digest: function,
        $destroy: function,
        $eval: function,
        $evalAsync: function,
        $$postDigest: function,
        $apply: function,
        $applyAsync: function,
        $on: function,
        $emit: function,
        $broadcast: function,         
      };
  
      var $rootScope = new Scope();
      }
    ];
  }

  

 

3. 核心实现

  3.1 监控对象属性

    $watch和$digest是相辅相成的。两者一起,构成了Angular作用域的核心:数据变化的响应。

  3.2 $watch

      作用为在Scope上添加一个监听器。当Scope上发生变更时,监听器会收到提示。监听器包括下面二个函数:

      • 一个监控函数,用于指定所关注的那部分数据。
      • 一个监听函数,用于在数据变更的时候接受提示。

      通常来说这里的监听器是监控一个表达式。监控表达式是一个字符串,比如说“{{user.firstName}}”,通常在数据绑定,指令的属性,或者JavaScript代码中指定,它被Angular解析和编译成一个监控函数。

      Angular框架中,双美元符前缀$$表示这个变量被当作私有的来考虑,不应当在外部代码中调用。

      $watch源码如下:

  $watch: function(watchExp, listener, objectEquality, prettyPrintExpression) {    
   var fn = isFunction(listener) ? listener : noop;
     var scope = this,
        array = scope.$$watchers,
        watcher = {
          fn: fn,
          last: initWatchVal,
          get: get,
          exp: prettyPrintExpression || watchExp,
          eq: !!objectEquality
        }; 
 
    array.unshift(watcher);
        
    return function deregisterWatch() {
      var index = arrayRemove(array, watcher);
      if (index >= 0) {
        incrementWatchersCount(scope, -1);
        if (index < array.$$digestWatchIndex) {
          array.$$digestWatchIndex--;
        }
      }
      lastDirtyWatch = null;
    };
  },

     该函数定义在原型上,为所有scope实例共用。

     该方法主要接受两个函数作参数(表达式和监听函数),把它们存储在$$watchers数组中array.unshift(watcher);

      Wather数组中的监听器将会被下面的digest函数调用。

  3.3 $digest

     Digest函数是angularjs的核心方法之一,进行脏值检查,代码如下: 

do { // "traverse the scopes" loop
  if ((watchers = current.$$watchers)) {
    watchers.$$digestWatchIndex = watchers.length;
    while (watchers.$$digestWatchIndex--) {
      try {
        watch = watchers[watchers.$$digestWatchIndex];
        // Most common watches are on primitives, in which case we can short
        // circuit it with === operator, only when === fails do we use .equals
        if (watch) {
          get = watch.get;
          if ((value = get(current)) !== (last = watch.last) &&
            !(watch.eq
              ? equals(value, last)
              : (isNumberNaN(value) && isNumberNaN(last)))) {
            dirty = true;
            lastDirtyWatch = watch;
            watch.last = watch.eq ? copy(value, null) : value;
            fn = watch.fn;
            fn(value, ((last === initWatchVal) ? value : last), current);
            if (ttl < 5) {
              logIdx = 4 - ttl;
              if (!watchLog[logIdx]) watchLog[logIdx] = [];
              watchLog[logIdx].push({
                msg: isFunction(watch.exp) ? 'fn: ' + (watch.exp.name || watch.exp.toString()) : watch.exp,
                newVal: value,
                oldVal: last
              });
            }
          } else if (watch === lastDirtyWatch) {
            // If the most recently dirty watcher is now clean, short circuit since the remaining watchers
            // have already been tested.
            dirty = false;
            break traverseScopesLoop;
          }
        }
      } catch (e) {
        $exceptionHandler(e);
      }
    }
  }
  // Insanity Warning: scope depth-first traversal
  // yes, this code is a bit crazy, but it works and we have tests to prove it!
  // this piece should be kept in sync with the traversal in $broadcast
  if (!(next = ((current.$$watchersCount && current.$$childHead) ||
    (current !== target && current.$$nextSibling)))) {
    while (current !== target && !(next = current.$$nextSibling)) {
      current = current.$parent;
    }
  }
} while ((current = next));

  

    Loop所有的watcher,通过get方法取得当前值,与 记录的上次值(watch.last)比较,如果不同则值为脏值。

    更新watch.last为当前新值,调用watcher上的监听函数,并把新值作为参数。

    注意:在监听函数中有可能会再次更改scope中的值,在最后一部分有个疯狂的深度优先遍历。这里会有个问题,如果一直有脏值怎么办,注意代码中有个TTL最大try的次数。

    以上实现了实现了Angular作用域的本质:添加监听器,在digest里运行它们。    

    特性:

    • 在作用域上添加数据本身并不会有性能折扣。如果没有监听器在监控某个属性,它在不在作用域上都无所谓。Angular并不会遍历作用域的属性,它遍历的是监听器。

    • $digest里会调用每个监控函数,因此,最好关注监听器的数量,还有每个独立的监控函数或者表达式的性能。

  3.4 $apply

    作用:集成外部代码与digest循环

    $apply使用函数作参数,它用$eval执行这个函数,然后通过$digest触发digest循环。    

      $apply: function(expr) {
        try {
          beginPhase('$apply');
          try {
            return this.$eval(expr);
          } finally {
            clearPhase();
          }
        } catch (e) {
          $exceptionHandler(e);
        } finally {
          try {
            $rootScope.$digest();
          } catch (e) {
            $exceptionHandler(e);
            // eslint-disable-next-line no-unsafe-finally
            throw e;
          }
        }
      },

   apply的源码非常简单,如上所示,通过eval调用指定的方法。并且保证执行后调用一次digest以进行脏值检查。

     apply使用时机:DOM事件、setTimeout、XHR或其他第三方的库,特别是异步方法对scope中数据的改变不会被watcher监控到,需要显示调用apply告诉angularjs。 

 

 以上就是angularJS核心思想及其源码实现,有点懒没有展开的说,也有可能理解不够透彻,毕竟是过时的框架了,作为前端编程思想进行学习而已。

 

refers:

https://www.cnblogs.com/leo_wl/p/3446075.html

https://www.cnblogs.com/wuya16/p/3769032.html

https://blog.csdn.net/u013510614/article/details/50703811

https://blog.csdn.net/u013510614/article/details/50703813

https://blog.csdn.net/u013510614/article/details/50703815

https://blog.csdn.net/cteng/article/details/72823190

https://github.com/xufei/Make-Your-Own-AngularJS/blob/master/01.md

posted on 2018-05-16 22:01  两只小蚂蚁  阅读(219)  评论(0编辑  收藏  举报