Angularjs directive全面解读(1.4.5)

时间:2021-12-28 08:07:56

说到Angularjs directive即指令,可以这么说Angularjs的灵魂就是指令,学会Angularjs指令那么你的Angularjs的武功就修炼了一半了,当然这只是鄙人的一点点独到见解,废话不多说,以下是Angularjs 指令的运用及源码实现解读。

学习Angularjs directive那么我们要带3个问题来学习:

1. 什么是angular 指令?
2. 指令如何定义存储的?
3. 指令如何编译运行的?

1.首先第一点解读什么是指令:

At a high level, directives are markers on a DOM element (such as an attribute, element name, comment or CSS class) that tell AngularJS's HTML compiler ($compile) to attach a specified behavior to that DOM element or even transform the DOM element and its children.(官网是这么说的)

指令就是一些附加在HTML元素上的自定义标记(例如:属性,元素,或css类),它告诉AngularJS的HTML编译器 ($compile) 在元素上附加某些指定的行为,甚至操作DOM、改变DOM元素,以及它的各级子节点。(这是鄙人通过解读官网定义的个人见解)

从中可以看出指令就是自定义的html标签用来解决反复的的CRUD(创建 读取 更改 删除)操作来修改DOM,这里小伙伴们如果想深入学习Angularjs操作DOM,那么小伙伴们首先需要了解D1、D2、以及D3实现了什么以及结构的优化有什么。再来学习Angularjs就会一步冲天!

好第一点我们知道了概念那么我们看看具体怎么使用Angularjs 指令吧:

指令实例:

<div ng-controller="Controller">
  Hello <input ng-model='name'> <hr/>
  <span self-app></span> <br/>
  <span self:app></span> <br/>
  <span self_app></span> <br/>
  <span data-self-app></span> <br/>
  <span x-self-app></span> <br/>
</div>

在这里可以看出如果指令注册为Attribute(属性)那么有5中合法的调用方式:

第一种以_来调用指

第二种以:来调用指令

第三种以-来调用指令

第四种以data-XXX-XXX来调用指令

第五种以x-XXX-XXX来调用指令

2.第二点解读指令如何定义存储的

指令通过

angular.module('selfApp', [])
.directive('myDirective', function() {
// 返回一个对象(暂且称之为指令对象)
  return {
    restrict: 'A',//E A M C
    replace: true,
    scope: true,
    template: '<span>hello world</span>',
    compile: function (tElement) {
      console.log('complile: ', tElement);
      return function (scope, elem) {
        console.log('1');
      }

}

}

});

这里我们可以看出来通过我们事先注册的module来第一了一个指令,我们如果想了解指令如何存储的 那么就要看看源代码了,

directive: invokeLaterAndSetModuleName('$compileProvider', 'directive')//angular.js 2172行

这是angularjs module的内部方法,

//延迟调用并设置模块名称

function invokeLaterAndSetModuleName(provider, method) {
  return function(recipeName, factoryFunction) {

//给工厂函数的module变量命名即给模块命名
    if (factoryFunction && isFunction(factoryFunction)) factoryFunction.$$moduleName = name;

//把我们注册的指令存储到调用队列中
      invokeQueue.push([provider, method, arguments]);

//返回我们的模块实例
    return moduleInstance;
  };
}

这里我们可以知道其实我们注册一个指令其实只是把我们的指令存储到了调用队列中等待调用。

3. 第三点解读指令如何编译运行的?

其实调用的时候就是编译Angularjs directive的时候也就是angularjs自己的编译机制,会使用$CompileProvider的directive方法来编译指令,compileProvider方法如下:

//这里其实是吧$CompileProvide注册为一个服务来调用

$CompileProvider.$inject = ['$provide', '$$sanitizeUriProvider'];//angular.js 6919行
function $CompileProvider($provide, $$sanitizeUriProvider) {

好了重头戏来了

// 指令存储容器,一个指令可以有多个指令工厂函数(即多个指令对象)

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',
        function($injector, $exceptionHandler) {

// 定义一个指令工厂集合
          var directives = [];

// 循环遍历指令工厂集合,并收集每个工厂函数返回的指令对象
          forEach(hasDirectives[name], function(directiveFactory, index) {
            try {

// 调用工厂函数,这里用的是$injector,所以工厂函数也可以是一个拥有依赖注入的函数或数组
              var directive = $injector.invoke(directiveFactory);

//检查指令是否是一个函数 即我们之前定义的指令其实是一个函数
              if (isFunction(directive)) {
                directive = { compile: valueFn(directive) };

//如果指令中存在compile函数就不运行link函数
              } 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);
              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;
};

好了源代码解读完成,大家是不是对指令有了一个系统的概念,而且也知道angularjs directive是怎么运行下来的了,那么总结一下:

1. 注册指令时,注册的是工厂函数(支持依赖注入),它负责返回指令对象
2. 一个指令可以注册多个工厂函数,就意味着将对应多个指令对象(即指令对象集合),其实多个指令对象之间是有一些冲突的,比如只能拥有有一个模板,拥有一个孤立作用域等
3. 一个指令对应的指令对象集合是通过注册为服务的方式被外界获取的

好,下面介绍定义一个指令时内部参数的解读:(作为一名专业的it工作者,做戏就要做全套!)

name -  当前scope的名称,注册时可以使用默认值(不填)。

priority - (优先级)- 当有多个directive定义在同一个DOM元素时,有时需要明确它们的执行顺序。这属性用于在directive的compile function调用之前进行排序。如果优先级相同,则执行顺序是                不确定的(经初步试验,优先级高的先执行,同级时按照类似栈的“后绑定先执行”。另外,测试时有点不小心,在定义directive的时候,两次定义了一个相同名称的directive,但执行结                  果发现,两个compile或者link function都会执行)。

terminal - (最后一组起作用)

- 如果设置为”true”,则表示当前的priority将会成为最后一组执行的directive。任何directive与当前的优先级相同的话,他们依然会执行,但顺序是不确定的(虽然顺序不确定,但基本上                   与priority的顺序一致。当前优先级执行完毕后,更低优先级的将不会再执行)。

scope -  true - 将为这个directive创建一个新的scope。如果在同一个元素中有多个directive需要新的scope的话,它还是只会创建一个scope。新的作用域规则不适用于根模版(root of the                           template),因此根模版往往会获得一个新的scope。这个scope是一个新的、独立(isolate)的scope。”isolate” scope与一般的scope的区别在于它不是通过原型继承于父scope的。这对                 于创建可复用的组件是很有帮助的,可以有效防止读取或者修改父级scope的数据。这个独立的scope会创建一个拥有一组来源于父scope的本地scope属性(local scope properties)的                     object hash。这些local properties对于为模版创建值的别名很有帮助(useful for aliasing values for templates!)。本地的定义是对其来源的一组本地scope property的hash映射(Locals                  definition is a hash of local scope property to its source))():

@或@attr - 建立一个local scope property到DOM属性的绑定。因为属性值总是String类型,所以这个值总是返回一个字符串。如果没有通过@attr指定属性名称,那么本地名称将与                                          DOM属性的名称一直。例如<widget my-attr='hello {{name}}'>,widget的scope定义为:{localName:’@myAttr’}。那么,widget scope property的localName会映射                                                 出”hello {{name}}"转换后的真实值。name属性值改变后,widget scope的localName属性也会相应地改变(仅仅单向,与下面的”=”不同)。name属性是在父scope读取                                         的(不是组件scope)

=或=expression(这里也许是attr) - 在本地scope属性与parent scope属性之间设置双向的绑定。如果没有指定attr名称,那么本地名称将与属性名称一致。例如<widget my-                                                   attr=”parentModel”>,widget定义的scope为:{localModel:’=myAttr’},那么widget scope property “localName”将会映射父scope的“parentModel”。如果parentModel发生                                       任何改变,localModel也会发生改变,反之亦然。(双向绑定)

&或&attr - 提供一个在父scope上下文中执行一个表达式的途径。如果没有指定attr的名称,那么local name将与属性名称一致。例如<widget my-attr=”count = count + value”>,                                             widget的scope定义为:{localFn:’increment()’},那么isolate scope property “localFn”会指向一个包裹着increment()表达式的function。一般来说,我们希望通过一个表达                                         式,将数据从isolate scope传到parent scope中。这可以通过传送一个本地变量键值的映射到表达式的wrapper函数中来完成。例如,如果表达式是increment(amount),那                                     么我们可以通过localFn({amount:22})的方式调用localFn以指定amount的值。

controller - controller 构造函数。controller会在pre-linking步骤之前进行初始化,并允许其他directive通过指定名称的require进行共享(看下面的require属性)。这将允许directive之间相互沟通,增强相互之间的行为。controller默认注入了以下本地对象到link:

$scope - 与当前元素结合的scope
    $element - 当前的元素
    $attrs - 当前元素的属性对象
    $transclude - 一个预先绑定到当前转置scope的转置linking function :function(cloneLinkingFn)。(A transclude;linking function pre-bound to the correct transclusion scope)

require - 请求另外的controller,传入当前directive的linking function中。require需要传入一个directive controller的名称。如果找不到这个名称对应的controller,那么将会抛出一个error。名称可以                加入以下前缀:

? - 不要抛出异常。这使这个依赖变为一个可选项。<br />
                                  ^ - 允许查找父元素的controller<br /><br />

restrict - EACM的子集的字符串,它限制directive为指定的声明方式。如果省略的话,directive将仅仅允许通过属性声明:E - 元素名称 A - 属性名 C - class名 M - 注释

template - 如果replace 为true,则将模版内容替换当前的HTML元素,并将原来元素的属性、class一并迁移;如果为false,则将模版元素当作当前元素的子元素处理。

templateUrl - 与template基本一致,但模版通过指定的url进行加载。因为模版加载是异步的,所以compilation、linking都会暂停,等待加载完毕后再执行。

replace - 如果设置为true,那么模版将会替换当前元素,而不是作为子元素添加到当前元素中。(注:为true时,模版必须有一个根节点)

transclude - 编译元素的内容,使它能够被directive所用。需要(在模版中)配合ngTransclude使用(引用)。transclusion的优点是linking function能够得到一个预先与当前scope绑定的transclusion                        function。一般地,建立一个widget,创建isolate scope,transclusion不是子级的,而是isolate scope的兄弟。这将使得widget拥有私有的状态,transclusion会被绑定到父级                                (preisolate)scope中。

true - 转换这个directive的内容。

                  ‘element’ - 转换整个元素,包括其他优先级较低的directive。(像将整体内容编译后,当作一个整体(外面再包裹p),插入到指定地方)

compile - compile function

link - link function,这个属性仅仅是在compile属性没有定义的情况下使用。

好了,以上内容就是鄙人的一些见解,望大家能够更好的了解angularjs directive的使用及原理。