angularjs源码笔记(1.3)--directive ctrl & attrs

前端之家收集整理的这篇文章主要介绍了angularjs源码笔记(1.3)--directive ctrl & attrs前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。

Controller & Attributes

1. Controller

1.1 配置使用

{
  controller: 'MyController',// 配置当前directive需要的controller
  require: '^myDir',// 通过配置require来获取该directive的controller
  link: function (scope,el,attrs,ctrls) {
    // ... ctrls就是require获取的ctrls
  }
}

1.2 源码

// 由于配置了templateUrl的directive进入异步阶段后会重新执行修改后的directive,
// 所以不需要在这作ctrl统计
if (!directive.templateUrl && directive.controller) {
  directiveValue = directive.controller;
  controllerDirectives = controllerDirectives || {};
  assertNoDuplicate("'" + directiveName + "' controller",controllerDirectives[directiveName],directive,$compileNode);
  controllerDirectives[directiveName] = directive;
}

收集是在compile阶段,但是真正的执行是在link阶段nodeLinkFn中,如下

if (controllerDirectives) {
  forEach(controllerDirectives,function(directive) {
    // locals提供了ctrl必要的scope,attrs等参数供ctrl中注入调用
    var locals = {
      $scope: directive === newIsolateScopeDirective || directive.$$isolateScope ? isolateScope : scope,$element: $element,$attrs: attrs,$transclude: transcludeFn
    },controllerInstance;

    controller = directive.controller;
    if (controller == '@') {
      controller = attrs[directive.name];
    }
   
    // 调用$controller服务生成ctrlInstance
    controllerInstance = $controller(controller,locals);
    
    // 缓存directive -> ctrl,为后续require作准备
    elementControllers[directive.name] = controllerInstance;
    if (!hasElementTranscludeDirective) {
      $element.data('$' + directive.name + 'Controller',controllerInstance);
    }
    
    // 处理配置了controllerAs,绑定到scope上
    if (directive.controllerAs) {
      locals.$scope[directive.controllerAs] = controllerInstance;
    }
  });
}

1.3. require

require定义在directive的配置中,源码处理逻辑:

1). 在applyDirectivesToNodeaddLinkFns中赋值给linkFn

pre.require = directive.require;

// ...

post.require = directive.require;

2). 在nodeLinkFn中正式使用刚才保存的require,通过getControllers()获取ctrls注入到linkFn的参数中

linkFn(linkFn.isolateScope ? isolateScope : scope,$element,linkFn.require && getControllers(linkFn.directiveName,linkFn.require,elementControllers),transcludeFn);

3). 详细见getControllers逻辑

function getControllers(directiveName,require,elementControllers) {
  var value,retrievalMethod = 'data',optional = false;
  if (isString(require)) {
    // 判断是否^(可从上层获取),及是否?(option可选)
    while((value = require.charAt(0)) == '^' || value == '?') {
      require = require.substr(1);
      if (value == '^') {
        retrievalMethod = 'inheritedData';
      }
      optional = optional || value == '?';
    }
    value = null;
 
    // 先本层获取ctrl
    if (elementControllers && retrievalMethod === 'data') {
      value = elementControllers[require];
    }
    // 没有需要的ctrl,就从上层获取
    value = value || $element[retrievalMethod]('$' + require + 'Controller');
 
    // 还是没获取到并且是非可选就抛错
    if (!value && !optional) {
      throw $compileMinErr('ctreq',"Controller '{0}',required by directive '{1}',can't be found!",directiveName);
    }
    return value;
  } else if (isArray(require)) {
    value = [];
    // array循环调用
    forEach(require,function(require) {
      value.push(getControllers(directiveName,elementControllers));
    });
  }
  return value;
}

2. Attributes

对attrs的处理分为收集监听及注入$attrs

2.1 收集attrs

ng在收集directive时顺便收集了attrs,所以都是在collectDirectives()

// 对所有的attribute进行循环
for (var attr,name,nName,ngAttrName,value,isNgAttr,nAttrs = node.attributes,j = 0,jj = nAttrs && nAttrs.length; j < jj; j++) {
  var attrStartName = false;
  var attrEndName = false;

  attr = nAttrs[j];
  if (!msie || msie >= 8 || attr.specified) {
    name = attr.name;
    value = trim(attr.value);

    // directiveNormalize 用于 xx-abc-d -> xxAbcD 转换
    ngAttrName = directiveNormalize(name);
    // 判断是否ngAttr开头的属性
    if (isNgAttr = NG_ATTR_BINDING.test(ngAttrName)) {
      // snake_case 是 directiveNormalize 反操作
      name = snake_case(ngAttrName.substr(6),'-');
    }

    var directiveNName = ngAttrName.replace(/(Start|End)$/,'');
    if (ngAttrName === directiveNName + 'Start') {
      attrStartName = name;
      attrEndName = name.substr(0,name.length - 5) + 'end';
      name = name.substr(0,name.length - 6);
    }

    nName = directiveNormalize(name.toLowerCase());
    // 缓存映射,paAttr -> pa-attr
    attrsMap[nName] = name;
    if (isNgAttr || !attrs.hasOwnProperty(nName)) {
        attrs[nName] = value;
        // 判断是否可以配置boolean的属性
        if (getBooleanAttrName(node,nName)) {
          attrs[nName] = true;
        }
    }
    addAttrInterpolateDirective(node,directives,nName);
    addDirective(directives,'A',maxPriority,ignoreDirective,attrStartName,attrEndName);
  }
}

这里有个地方,何为可以配置为boolean的属性呢?

function getBooleanAttrName(element,name) {
  
  var booleanAttr = BOOLEAN_ATTR[name.toLowerCase()];

  return booleanAttr && BOOLEAN_ELEMENTS[nodeName_(element)] && booleanAttr;
}

// BOOLEAN_ATTR > multiple,selected,checked,disabled,readOnly,required,open
// BOOLEAN_ELEMENTS > input,select,option,textarea,button,form,details

2.2 $attrs源码

主要分析如何实现监听

$observe: function(key,fn) {
  var attrs = this,$$observers = (attrs.$$observers || (attrs.$$observers = {})),listeners = ($$observers[key] || ($$observers[key] = []));

  listeners.push(fn);
  // 使用$evalAsync为了初始化时也能触发执行fn
  $rootScope.$evalAsync(function() {
    if (!listeners.$$inter) {
      fn(attrs[key]);
    }
  });
  
  // 跟$watch类似返回removeObserve方法调用后移除监听
  return function() {
    arrayRemove(listeners,fn);
  };
}
$set: function(key,writeAttr,attrName) {
  
  // ...省略

  var $$observers = this.$$observers;
  $$observers && forEach($$observers[observer],function(fn) {
    try {
      fn(value);
    } catch (e) {
      $exceptionHandler(e);
    }
  });
}

3. $controller

ng中很大一部分逻辑都是写在controller中,下面对ctrl的注册及使用作分析

3.1 注册ctrl

module.controller('ctrlName',function () {
  // ...
});

该controller方法是$controllerProvider中的register提供,具体怎么连接起来的后续在inject中作分析

function $ControllerProvider() {
  var controllers = {};

  this.register = function(name,constructor) {
    assertNotHasOwnProperty(name,'controller');
    if (isObject(name)) {
      extend(controllers,name);
    } else {
      controllers[name] = constructor;
    }
  };
  
  // ... 省略
}

其实就是将name -> fn的映射保存下来

3.2 实例化ctrl

由于本质上也是个service所以定义provider就是$get,如下:

this.$get = ['$injector','$window',function($injector,$window) {
  return function(expression,locals) {
    var instance,match,constructor,identifier;

    if(isString(expression)) {
      // 检验是否符合 ctrl as aCtrl 这样的特性
      match = expression.match(CNTRL_REG),constructor = match[1],identifier = match[3];
      // 查找的顺序是已注册的controllers中 > $scope > window全局 
      expression = controllers.hasOwnProperty(constructor)
          ? controllers[constructor]
          : getter(locals.$scope,true) || getter($window,true);
      // 判断expression是否为空
      assertArgFn(expression,true);
    }
    
    // 实例化ctrl,由于需要注入arg所以需要用$injector进行实例化
    instance = $injector.instantiate(expression,locals,constructor);

    if (identifier) {
      if (!(locals && typeof locals.$scope === 'object')) {
        throw minErr('$controller')('noscp',"Cannot export controller '{0}' as '{1}'! No $scope object provided via `locals`.",constructor || expression.name,identifier);
      }

      locals.$scope[identifier] = instance;
    }

    return instance;
  };
}];
原文链接:https://www.f2er.com/angularjs/149038.html

猜你在找的Angularjs相关文章