#4 指令compile和link过程详解

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

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

单一指令的过程如下图:

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
<div my-directive text="first"></div>

// 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
<div my-directive text="{{i}}" ng-repeat="i in [1, 2, 3]"></div>

// 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的完整过程

嵌套指令编译和link过程.jpg

示例:

// html
<div my-directive text="first">
  <div my-directive text="..second">
    <div my-directive text="...three"></div>
  </div>
</div>

// 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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,835评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,598评论 1 295
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,569评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,159评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,533评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,710评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,923评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,674评论 0 203
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,421评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,622评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,115评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,428评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,114评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,097评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,875评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,753评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,649评论 2 271

推荐阅读更多精彩内容