angular源碼分析:$compile服務——directive他媽


一、directive的注冊

1.我們知道,我們可以通過類似下面的代碼定義一個指令(directive)。

var myModule = angular.module(...);

  myModule.directive('directiveName', function factory(injectables) {
    var directiveDefinitionObject = {
      priority: 0,
      template: '<div></div>', // or // function(tElement, tAttrs) { ... },
      // or
      // templateUrl: 'directive.html', // or // function(tElement, tAttrs) { ... },
      transclude: false,
      restrict: 'A',
      scope: false,
      controller: function($scope, $element, $attrs, $transclude, otherInjectables) { ... },
      controllerAs: 'stringAlias',
      require: 'siblingDirectiveName', // or // ['^parentDirectiveName', '?optionalDirectiveName', '?^optionalParent'],
      compile: function compile(tElement, tAttrs, transclude) {
        return {
          pre: function preLink(scope, iElement, iAttrs, controller) { ... },
          post: function postLink(scope, iElement, iAttrs, controller) { ... }
        }
        // or
        // return function postLink( ... ) { ... }
      },
      // or
      // link: {
      //  pre: function preLink(scope, iElement, iAttrs, controller) { ... },
      //  post: function postLink(scope, iElement, iAttrs, controller) { ... }
      // }
      // or
      // link: function postLink( ... ) { ... }
    };
    return directiveDefinitionObject;
  });

通過前面的分析(directive: invokeLater('$compileProvider', 'directive')),我們可以知道上面的代碼會最終調用$compileProvider.directive

2.$compileProvider.directive


  var hasDirectives = {},//定義用於存儲指令的對象
      Suffix = 'Directive',
      COMMENT_DIRECTIVE_REGEXP = /^\s*directive\:\s*([\w\-]+)\s+(.*)$/,
      CLASS_DIRECTIVE_REGEXP = /(([\w\-]+)(?:\:([^;]+))?;?)/,
      ALL_OR_NOTHING_ATTRS = makeMap('ngSrc,ngSrcset,src,srcset'),
      REQUIRE_PREFIX_REGEXP = /^(?:(\^\^?)?(\?)?(\^\^?)?)?/;
  /**
   * @ngdoc method
   * @name $compileProvider#directive
   * @kind function
   *
   * @description
   * Register a new directive with the compiler.
   *
   * @param {string|Object} name Name of the directive in camel-case (i.e. <code>ngBind</code> which
   *    will match as <code>ng-bind</code>), or an object map of directives where the keys are the
   *    names and the values are the factories.
   * @param {Function|Array} directiveFactory An injectable directive factory function. See
   *    {@link guide/directive} for more info.
   * @returns {ng.$compileProvider} Self for chaining.
   */
   this.directive = function registerDirective(name, directiveFactory) {
    assertNotHasOwnProperty(name, 'directive');
    if (isString(name)) {
      assertValidDirectiveName(name);
      assertArg(directiveFactory, 'directiveFactory');
      if (!hasDirectives.hasOwnProperty(name)) {
        hasDirectives[name] = [];
        $provide.factory(name + Suffix, ['$injector', '$exceptionHandler',//這里在定義指令的服務,比如注冊了`test`這樣的一個指令,這里就會定義個`testDirective`的服務.
          function($injector, $exceptionHandler) {//注意這個匿名函數,將在需要獲取指令的是否被執行,所以當這個函數執行時,所有的指令的定義都放入了hasDirectives數組
            var directives = [];
            forEach(hasDirectives[name], function(directiveFactory, index) {//注意這里的directiveFactory是從數組中取出的元素而不是前面函數的參數
              try {
                var directive = $injector.invoke(directiveFactory);
                if (isFunction(directive)) {
                  directive = { compile: valueFn(directive) };
                } else if (!directive.compile && directive.link) {
                  directive.compile = valueFn(directive.link);
                }
                directive.priority = directive.priority || 0;
                directive.index = index;
                directive.name = directive.name || name;
                directive.require = directive.require || (directive.controller && directive.name);
                directive.restrict = directive.restrict || 'EA';
                var bindings = directive.$$bindings =
                    parseDirectiveBindings(directive, directive.name);//這個parseDirectiveBindings需要分析
                if (isObject(bindings.isolateScope)) {
                  directive.$$isolateBindings = bindings.isolateScope;
                }
                directive.$$moduleName = directiveFactory.$$moduleName;
                directives.push(directive);
              } catch (e) {
                $exceptionHandler(e);
              }
            });
            return directives;
          }]);
      }
      hasDirectives[name].push(directiveFactory);//這里可以看到指令可以同名,一個指令名對應的是一個指令數組
    } else {
      forEach(name, reverseParams(registerDirective));//可以數組的方式,成批量的注冊指令
    }
    return this;
  };

請注意代碼的執行數序。
a.在第一注冊某個執行時(比如現在注冊了兩個test執行),那么第一次調用這個函數注冊指令時,會定一個testDirective的服務,且將該指令的工廠函數壓入hasDirectives['test']
b.當再次注冊一個與test同名的另一個指令時,僅是將其工廠函數壓入hasDirectives['test']
c.當指令需要時,框架會調用testDirectiveProvider.$get(也就是testDirective的工廠方法)制造一個directives數組

3.parseDirectiveBindings

  function parseIsolateBindings(scope, directiveName, isController) {
    var LOCAL_REGEXP = /^\s*([@&]|=(\*?))(\??)\s*(\w*)\s*$/;

    var bindings = {};

    forEach(scope, function(definition, scopeName) {
      var match = definition.match(LOCAL_REGEXP);

      if (!match) {
        throw $compileMinErr('iscp',
            "Invalid {3} for directive '{0}'." +
            " Definition: {... {1}: '{2}' ...}",
            directiveName, scopeName, definition,
            (isController ? "controller bindings definition" :
            "isolate scope definition"));
      }

      bindings[scopeName] = {
        mode: match[1][0],
        collection: match[2] === '*',
        optional: match[3] === '?',
        attrName: match[4] || scopeName
      };
    });

    return bindings;
  }

  function parseDirectiveBindings(directive, directiveName) {
    var bindings = {
      isolateScope: null,
      bindToController: null
    };
    if (isObject(directive.scope)) {//指令對象中scope==true 或者 scope是一個對象時 
      if (directive.bindToController === true) {
        bindings.bindToController = parseIsolateBindings(directive.scope, //解析scope對象中的表達式
                                                         directiveName, true);
        bindings.isolateScope = {};
      } else {
        bindings.isolateScope = parseIsolateBindings(directive.scope,
                                                     directiveName, false);
      }
    }
    if (isObject(directive.bindToController)) {
      bindings.bindToController =
          parseIsolateBindings(directive.bindToController, directiveName, true);
    }
    if (isObject(bindings.bindToController)) {
      var controller = directive.controller;
      var controllerAs = directive.controllerAs;
      if (!controller) {
        // There is no controller, there may or may not be a controllerAs property
        throw $compileMinErr('noctrl',
              "Cannot bind to controller without directive '{0}'s controller.",
              directiveName);
      } else if (!identifierForController(controller, controllerAs)) {
        // There is a controller, but no identifier or controllerAs property
        throw $compileMinErr('noident',
              "Cannot bind to controller without identifier for directive '{0}'.",
              directiveName);
      }
    }
    return bindings;
  }

二、給出一幅圖說明angular的"編譯原理"

1.在定義或者注冊指令,最終是以延遲調用$compileProvider.Directive來完成 2.編譯階段,主要工作是收集dom元素上引用到的指令,編譯函數將返回一個"鏈接函數"用戶完成和$scope的鏈接. 3.鏈接過程,將$scope與dom建立聯系,指令指令中定義的link函數

由於$compile這部分的代碼過於復雜,本期暫且講到這里,下期繼續
上一期:angular源碼分析:angular中臟活累活的承擔者之$interpolate
下一期:angular源碼分析:$compile服務——指令的編寫


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM