#4 指令compile和link过程详解

指令的编译(compile)和link过程分为:

  1. compile 阶段
  2. link 阶段,这个阶段又分为:
    1. controller 阶段
    2. pre link 阶段
    3. post link 阶段 (也称为 linking)

单一指令的过程如下图:

#4 指令compile和link过程详解_第1张图片
compile && link 阶段.jpg

可以看出各个阶段的特点:

Compile

这个阶段可以有2个参数: tElementtAttributes,(t 表示template模版的意思)下面是这个阶段的特点:

  • 加载和遍历模版DOM
  • 只执行一次
  • 返回link函数,或一个对象(包含 pre, post(或者称为link))
  • 不存在 Scope
  • 模版实例或克隆模版还未被创建
  • 可以做一些所有实例或克隆模版能共享的操作
  • 能够操作DOM模版
  • 不能使用克隆的数据或事件
  • 不能获取克隆的DOM

Controller

这个阶段执行指令自身控制器,这个阶段有如下特点:

  • 第一个执行实例或克隆的模版,一般使用 $element, $attributes, $scope命名
  • 创建Scope(scope的初始化)
  • 能够操作模版实例的数据
  • 不推荐操作实例DOM

Pre

这个阶段一般使用 iElement, iAttributes, scope命名(i 表示instance实例的意思),这个阶段有如下特点:

  • 对每个实例,controller阶段完成之后这个阶段立刻执行(后面嵌套指令会谈到)
  • 可以对DOM模版进行引用
  • 实例作用域已准备
  • 实例还没有linked到作用域,因此不能设置绑定(bindings)
  • 子元素或指令还没有准备好(后面嵌套指令会谈到)
  • 能够操作Scope
  • 在这个阶段设置数据很安全,甚至可以设置子元素数据
  • 操作DOM实例还不是很安全
  • 不能访问子元素

Post

这个阶段相当于通常写的 link 函数,有如下特点:

  • 每个实例的最后一个阶段,也称之为 linkng 或者 link 函数
  • 可以对DOM模版进行引用
  • 作用域和实例linked(和数据绑定)
  • 子元素或子指令已经准备好(或者说已经linked)
  • 能够操作Scope
  • 添加事件,观察子元素都很安全
  • 能安全的操作DOM实例
  • 设置子元素数据还不是很安全

上面几个阶段就是指令被编译和link的过程,示例如下:

// html
// app.js angular .module('app', []) .directive('myDirective', function() { return { // 1. 编译阶段 第1步 // 注意参数 'tElement'的 't' 表示 模版的意思 compile: function(tElement, tAttributes) { console.log(tAttributes.text + ' - compile 阶段'); // 返回一个对象(pre和post) 或 函数(link函数) return { // 3. 可以访问scope 第3步 // 注意参数 'iElement'的 'i' 表示 实例的意思 pre: function(scope, iElement, iAttributes) { console.log(iAttributes.text + ' - pre 阶段'); }, // 4. 相当于link函数 第4步 post: function(scope, iElement, iAttributes) { console.log(iAttributes.text + ' - post 阶段'); } }; }, // 2. controller阶段 第2步 注意参数 controller: function($scope, $element, $attrs) { console.log($attrs.text + ' - controller 阶段'); } } }) // 最后输出结果为 first - compile 阶段 first - controller 阶段 first - pre 阶段 first - post 阶段

另一示例:

// html
// js 同上 // 最后输出结果为 {{i}} - compile 编译 {{i}} - controller 编译 // {{i}} 没有被计算 1 - pre 编译 1 - post 编译 {{i}} - controller 编译 2 - pre 编译 2 - post 编译 {{i}} - controller 编译 3 - pre 编译 3 - post 编译

可以看出controller阶段,i的值还没有计算,如果想要i在controller阶段被计算出来,则需要使用 $interpolate(插值服务)

// html 同上

// js
angular
  .module('app', [])
  .directive('myDirective', function($interpolate) { // 引入$interpolate服务
    return {
      compile: function(tElement, tAttributes) {
        console.log(tAttributes.text + ' - compile 阶段');
    
        return {
          pre: function(scope, iElement, iAttributes, controller) {
            console.log(iAttributes.text + ' - pre 阶段');
          },
          
          post: function(scope, iElement, iAttributes, controller) {
            console.log(iAttributes.text + ' - post 阶段');
          }
        };
      },

      controller: function($scope, $element, $attrs) {
        // 使用插值服务计算 '{{i}}' 的值
        // 注意后面的 $scope 表示对该作用域
        var v = $interpolate($attrs.text)($scope);
        console.log(v + ' - controller 阶段');
      }
    }
  })

// 最后输出结果为
{{i}} - compile 编译  
1 - controller 编译
1 - pre 编译
1 - post 编译
2 - controller 编译
2 - pre 编译
2 - post 编译
3 - controller 编译
3 - pre 编译
3 - post 编译

对于嵌套指令,内部执行过程如下图

看着数字走,即是嵌套指令编译和link的完整过程

#4 指令compile和link过程详解_第2张图片
嵌套指令编译和link过程.jpg

示例:

// html
// js代码同上 // 输出结果 first - compile 阶段 // 从外到内 ..second - compile 阶段 ...three - compile 阶段 first - controller 阶段 // 从外到内 first - pre 阶段 ..second - controller 阶段 ..second - pre 阶段 ...three - controller 阶段 ...three - pre 阶段 ...three - post 阶段 // 从内到外 ..second - post 阶段 first - post 阶段

可以看出这个过程和图中的过程是一致的:

  • 首先从外到内进行编译阶段
  • 然后从外到内进行(controller阶段 -> pre阶段)这是一个整体
  • 最后从内到外完成post阶段,即从子元素或子指令最先开始link

compile 的几种写法

如果值希望使用compile 和 post, controller

.directive('myDirective', function($interpolate) {
    return {
      compile: function(tElement, tAttributes) {
        console.log(tAttributes.text + ' - compile 阶段');
        
        // 给模版添加样式,则后面的实例都可以使用
        tElement.css('border', '1px solid black');

        // 表示post函数
        return function(scope, iElement, iAttributes, controller) {
          console.log(iAttributes.text + ' - post 阶段');
          if (iAttributes.text === '3') {
            // 对text值为3的元素添加背景颜色
            iElement.css('background-color', 'green');
          }
        };
        
      },

      controller: function($scope, $element, $attrs) {
        var v = $interpolate($attrs.text)($scope);
        console.log(v + ' - controller 阶段');
      }
    }
  })

如果希望保留pre 和 post, controller, 而不需要compile

这种情况可以返回一个link 对象,即可:

.directive('myDirective', function($interpolate) {
    return {
      link: {
        pre: function(scope, iElement, iAttributes, controller) {
            console.log(iAttributes.text + ' - pre 阶段');
          },
        post: function(scope, iElement, iAttributes, controller) {
          console.log(iAttributes.text + ' - post 阶段');
          if (iAttributes.text === '3') {
            // 对text值为3的元素添加背景颜色
            iElement.css('background-color', 'green');

            iElement.on('click', scope.handleClick);
        }     
      },

      controller: function($scope, $element, $attrs) {
        var v = $interpolate($attrs.text)($scope);
        console.log(v + ' - controller 阶段');
        // listener handler
        $scope.handleClick = function() {
          alert($attrs.text);
        }
      }
    }
  })

如果只需要post函数,不需要controller等

.directive('myDirective', function() {
    // 表示post函数
    return function(scope, iElement, iAttributes, controller) {
            console.log(iAttributes.text + ' - post 阶段');
            if (iAttributes.text === '3') {
            // 对text值为3的元素添加背景颜色
            iElement.css('background-color', 'green');
          }
  })

总结

理解这一过程对理解指令是如何运作的使用有帮助。具体youtube - Compile and Link in depth

你可能感兴趣的:(#4 指令compile和link过程详解)