AngularJS 1.x 思惟索引

 

  咱們在這裏不討論Angular2和Angular4,由於其徹底重寫,其實已經不叫AngularJS了。javascript

  AngularJS的缺陷:html

    • 性能問題:經過檢查髒值進行數據更新,當數據不斷增長時,檢查的效率就不斷下降。頁面加載速度也會變慢。
    • 落後於當前web發展理念(如組件式的開發)
    • 對手機端的支持不是太友好        

1. 執行時機

  angularJS定義是一個當即執行的匿名函數,那麼其執行時機爲引入angularJS的位置決定。前端

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

2. 源碼結構

  基於1.6.9java

  源碼的上部分基本在定義方法,其主要執行開始在源碼的最後,主要函數以下:  git

bindJQuery();

publishExternalAPI(angular);

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

  2.1 bindJQuery:

    嘗試綁定jQuery對象,若是沒有則採用內置的jqLiteangularjs

  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方法。  github

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函數主要功能:建立注入器和用注入器進行調用web

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函數。具體分析以下bootstrap

  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

相關文章
相關標籤/搜索