Angular.js源码分析之ngcontroller

在angular中用的最多的指令可能就是ng-controller,下边就一起来分析下这个指令。

先来看下源码:

var ngControllerDirective = [function() {
  return {
    restrict: 'A',
    scope: true,
    controller: '@',
    priority: 500
  };
}];

可以看到代码是很简单的,这里可能唯一需要注意的细节就是controller: '@'这个是神马意思?其实这个逻辑在之前分析link中分析nodeLinkFn的时候其中有setupControllers这个函数,他有这样的逻辑:

// 在controller中可以注入的东西
var locals = {
  $scope: directive === newIsolateScopeDirective || directive.$$isolateScope ? isolateScope : scope,
  $element: $element,
  $attrs: attrs,
  $transclude: transcludeFn
};
// 省略
var controller = directive.controller;
if (controller == '@') {
  // ngController设置的controller的值是@
  // 需要找回属性中的该值
  controller = attrs[directive.name];
}

// 调用$controller服务 但是 是延迟的
var controllerInstance = $controller(controller, locals, true, directive.controllerAs);

所以上边的指令中controller的意思就很明显了,就是取得其属性(ng-controller)值。但是后边的一行逻辑值得我们注意,那就是$controller服务。

$controller

先看代码:

var CNTRL_REG = /^(\S+)(\s+as\s+(\w+))?$/;
/**
 * controller Provider
 * 用来创建新的controller
 */
function $ControllerProvider() {
  var controllers = {},
      globals = false;
	
	// 注册controller 其实Module实例的controller方法就是利用这个注册的
  this.register = function(name, constructor) {
    assertNotHasOwnProperty(name, 'controller');
    if (isObject(name)) {
      extend(controllers, name);
    } else {
      controllers[name] = constructor;
    }
  };
	
	// 设置允许从全局中查找controller定义
  this.allowGlobals = function() {
    globals = true;
  };

  this.$get = ['$injector', '$window', function($injector, $window) {
		
		// provider实例
    return function(expression, locals, later, ident) {
      // 私有API:
      //   param `later` --- 如果是later的话 也就意味着不是立即调用$injector.instantiate得到实例
      //                     而是加一个闭包 返回一个函数 当这个函数再次被调用的时候才返回真正的实例
      //                     主要用在$compile中绑定独立scope的时候
      //   param `ident` --- 其实就是controller as ident的东东
      var instance, match, constructor, identifier;
      later = later === true;
      if (ident && isString(ident)) {
        identifier = ident;
      }

      if (isString(expression)) {
      	// 如果是字符串 则取得在controllers中对应key的定义值
        match = expression.match(CNTRL_REG);
        if (!match) {
          throw $controllerMinErr('ctrlfmt',
            "Badly formed controller string '{0}'. " +
            "Must match `__name__ as __id__` or `__name__`.", expression);
        }
        constructor = match[1],
        // controller as 语法支持
        identifier = identifier || match[3];
        expression = controllers.hasOwnProperty(constructor)
            ? controllers[constructor]
            : getter(locals.$scope, constructor, true) ||
                (globals ? getter($window, constructor, true) : undefined);

        assertArgFn(expression, constructor, true);
      }

      if (later) {
        // later的话 就加一层闭包
        // 这个函数继承controller函数的prototype
        var controllerPrototype = (isArray(expression) ?
          expression[expression.length - 1] : expression).prototype;
        instance = Object.create(controllerPrototype || null);

        if (identifier) {
          addIdentifier(locals, identifier, instance, constructor || expression.name);
        }

        var instantiate;
        return instantiate = extend(function() {
          var result = $injector.invoke(expression, instance, locals, constructor);
          if (result !== instance && (isObject(result) || isFunction(result))) {
            instance = result;
            if (identifier) {
              // If result changed, re-assign controllerAs value to scope.
              addIdentifier(locals, identifier, instance, constructor || expression.name);
            }
          }
          return instance;
        }, {
          instance: instance,
          identifier: identifier
        });
      }

      instance = $injector.instantiate(expression, locals, constructor);

      if (identifier) {
        addIdentifier(locals, identifier, instance, constructor || expression.name);
      }

      return instance;
    };

    // controller as的语法
    // 可以看到其实也就是在$scope中加了一下identifier的值为instance
    function addIdentifier(locals, identifier, instance, name) {
      if (!(locals && isObject(locals.$scope))) {
        throw minErr('$controller')('noscp',
          "Cannot export controller '{0}' as '{1}'! No $scope object provided via `locals`.",
          name, identifier);
      }

      locals.$scope[identifier] = instance;
    }
  }];
}
// 根据path得到obj对象上的值
// 类似于命名空间的概念
// obj = {a:{b:{}}}
// getter(obj, 'a.b')
//TODO(misko): this function needs to be removed
function getter(obj, path, bindFnToScope) {
  if (!path) return obj;
  var keys = path.split('.');
  var key;
  var lastInstance = obj;
  var len = keys.length;

  for (var i = 0; i < len; i++) {
    key = keys[i];
    if (obj) {
      obj = (lastInstance = obj)[key];
    }
  }
  if (!bindFnToScope && isFunction(obj)) {
    return bind(lastInstance, obj);
  }
  return obj;
}

其实整体逻辑还是不复杂的,主要是调用$injector.instantiate这个方法来实例化定义的controller,只是这个时候传入了重要的locals,而locals包含什么呢?就是上边在分析controller: '@'这个的意思的时候上边的那部分代码:

// 在controller中可以注入的东西
var locals = {
  $scope: directive === newIsolateScopeDirective || directive.$$isolateScope ? isolateScope : scope,
  $element: $element,
  $attrs: attrs,
  $transclude: transcludeFn
};

结语

到这里就分析完了ng-controller指令,其中重点是分析了$controller这个服务。整体还好,没有复杂逻辑,还是比较容易理解的。下篇准备分析下比较特殊的一个指令ng-if,去一探究竟。

发布于: 2015年 10月 26日