angularjs指令中的compile与link函数详解


Posted in Javascript onDecember 06, 2014

通常大家在使用ng中的指令的时候,用的链接函数最多的是link属性,下面这篇文章将告诉大家complie,pre-link,post-link的用法与区别.

angularjs里的指令非常神奇,允许你创建非常语义化以及高度重用的组件,可以理解为web components的先驱者.

网上已经有很多介绍怎么使用指令的文章以及相关书籍,相互比较的话,很少有介绍compile与link的区别,更别说pre-link与post-link了.

大部分教程只是简单的说下compile会在ng内部用到,而且建议大家只用link属性,大部分指令的例子里都是这样的

这是非常不幸的,因为正确的理解这些函数的区别会提高你对ng内部运行机理的理解,有助于你开发更好的自定义指令.

所以跟着我一起来看下面的内容一步步的去了解这些函数是什么以及它们应该在什么时候用到

本文假设你已经对指令有一定的了解了,如果没有的话强烈建议你看看这篇文章AngularJS developer guide section on directives

 NG中是怎么样处理指令的

开始分析之前,先让我们看看ng中是怎么样处理指令的.

当浏览器渲染一个页面时,本质上是读html标识,然后建立dom节点,当dom树创建完毕之后广播一个事件给我们.

当你在页面中使用script标签加载ng应用程序代码时,ng监听上面的dom完成事件,查找带有ng-app属性的元素.

当找到这样的元素之后,ng开始处理dom以这个元素的起点,所以假如ng-app被添加到html元素上,则ng就会从html元素开始处理dom.

从这个起点开始,ng开始递归查找所有子元素里面,符合应用程序里定义好的指令规则.

ng怎样处理指令其实是依赖于它定义时的对象属性的,你可以定义一个compile或者一个link函数,或者用pre-link和post-link函数来代替link.

所以这些函数的区别呢?为什么要使用它?以及什么时候使用它呢?

带着这些问题跟着我一步一步来解答这些迷团吧

一段代码

为了解释这些函数的区别,下面我将使用一个简单易懂的例子

1.如果您有任何的问题,请不要犹豫赶紧在下面加上你的评论吧.

看看下面一段html标签代码

  <level-one>

        <level-two>

            <level-three>

                Hello 

            </level-three>

        </level-two>

    </level-one>

然后是一段js代码

var app = angular.module('plunker', []);
    function createDirective(name){

      return function(){

        return {

          restrict: 'E',

          compile: function(tElem, tAttrs){

            console.log(name + ': compile');

            return {

              pre: function(scope, iElem, iAttrs){

                console.log(name + ': pre link');

              },

              post: function(scope, iElem, iAttrs){

                console.log(name + ': post link');

              }

            }

          }

        }

      }

    }
    app.directive('levelOne', createDirective('levelOne'));

    app.directive('levelTwo', createDirective('levelTwo'));

    app.directive('levelThree', createDirective('levelThree'));

结果非常简单:让ng来处理三个嵌套指令,并且每个指令都有自己的complile,pre-link,post-link函数,每个函数都会在控制台里打印一行东西来标识自己.

这个例子能够让我们简单的了解到ng在处理指令时,内部的流程

代码输出

下面是一个在控制台输出结果的截图

angularjs指令中的compile与link函数详解

如果想自己试一下这个例子的话,请点击this plnkr,然后在控制台查看结果.

分析代码

第一个要注意的是这些函数的调用顺序:

 // COMPILE PHASE

    // levelOne:    compile function is called

    // levelTwo:    compile function is called

    // levelThree:  compile function is called
    // PRE-LINK PHASE

    // levelOne:    pre link function is called

    // levelTwo:    pre link function is called

    // levelThree:  pre link function is called
    // POST-LINK PHASE (Notice the reverse order)

    // levelThree:  post link function is called

    // levelTwo:    post link function is called

    // levelOne:    post link function is called

这个例子清晰的显示出了ng在link之前编译所有的指令,然后link要又分为了pre-link与post-link阶段.

注意下,compile与pre-link的执行顺序是依次执行的,但是post-link正好相反.

所以上面已经明确标识出了不同的阶段,但是compile与pre-link有什么区别呢,都是相同的执行顺序,为什么还要分成两个不同的函数呢?

DOM

为了挖的更深一点,让我们简单的修改一下上面的代码,它也会在各个函数里打印参数列表中的element变量

var app = angular.module('plunker', []);
    function createDirective(name){ 

      return function(){

        return {

          restrict: 'E',

          compile: function(tElem, tAttrs){

            console.log(name + ': compile => ' + tElem.html());

            return {

              pre: function(scope, iElem, iAttrs){

                console.log(name + ': pre link => ' + iElem.html());

              },

              post: function(scope, iElem, iAttrs){

                console.log(name + ': post link => ' + iElem.html());

              }

            }

          }

        }

      }

    }
    app.directive('levelOne', createDirective('levelOne'));

    app.directive('levelTwo', createDirective('levelTwo'));

    app.directive('levelThree', createDirective('levelThree'));

注意下console.log里的输出,除了输出原始的html标记基本没别的改变.

这个应该能够加深我们对于这些函数上下文的理解.

再次运行代码看看

输出

下面是一个在控制台输出结果的截图

angularjs指令中的compile与link函数详解

假如你还想自己运行看看效果,可以点击this plnkr,然后在控制台里查看输出结果.

观察

输出dom的结果可以暴露一些有趣的事情:dom内容在compile与pre-link两个函数中是不一样的

所以发生了什么呢?

Compile

我们已经知道当ng发现dom构建完成时就开始处理dom.

所以当ng在遍历dom的时候,碰到level-one元素,从它的定义那里了解到,要执行一些必要的函数

因为compile函数定义在level-one指令的指令对象里,所以它会被调用并传递一个element对象作为它的参数

如果你仔细观察,就会看到,浏览器创建这个element对象时,仍然是最原始的html标记

1.在ng中,原始dom通常用来标识template element,所以我在定义compile函数参数时就用到了tElem名字,这个变量指向的就是template element.

一旦运行levelone指令中的compile函数,ng就会递归深度遍历它的dom节点,然后在level-two与level-three上面重复这些操作.

Post-link

深入了解pre-link函数之前,让我们来看看post-link函数.

2.如果你在定义指令的时候只使用了一个link函数,那么ng会把这个函数当成post-link来处理,因此我们要先讨论这个函数
当ng遍历完所有的dom并运行完所有的compile函数之后,就反向调用相关联的post-link函数.

dom现在开始反向,并执行post-link函数,因此,在之前这种反向的调用看起来有点奇怪,其实这样做是非常有意义的.

angularjs指令中的compile与link函数详解

当运行包含子指令的指令post-link时,反向的post-link规则可以保证它的子指令的post-link是已经运行过的.

所以,当运行level-one指令的post-link函数的时候,我们能够保证level-two和level-three的post-link其实都已经运行过了.

这就是为什么人们都认为post-link是最安全或者默认的写业务逻辑的地方.

但是为什么这里的element跟compile里的又不同呢?

一旦ng调用过指令的compile函数,就会创建一个template element的element实例对象,并且为它提供一个scope对象,这个scope有可能是新实例,也有可能是已经存在,可能是个子scope,也有可能是独立的scope,这些都得依赖指令定义对象里的scope属性值

所以当linking发生时,这个实例element以及scope对象已经是可用的了,并且被ng作为参数传递到post-link函数的参数列表中去.

1.我个人总是使用iElem名称来定义一个link函数的参数,并且它是指向element实例的

所以post-link(pre-link)函数的element参数对象是一个element实例而不是一个template element.

所以上面例子里的输出是不同的

Pre-link

当写了一个post-link函数,你可以保证在执行post-link函数的时候,它的所有子级指令的post-link函数是已经执行过的.

在大部分的情况下,它都可以做的更好,因此通常我们都会使用它来编写指令代码.

然而,ng为我们提供了一个附加的hook机制,那就是pre-link函数,它能够保证在执行所有子指令的post-link函数之前.运行一些别的代码.

这句话是值得反复推敲的

pre-link函数能够保证在element实例上以及它的所有子指令的post-link运行之前执行.

所以它使的post-link函数反向执行是相当有意义的,它自己是原始的顺序执行pre-link函数

这也意为着pre-link函数运行在它所有子指令的pre-link函数之前,所以完整的理由就是:

一个元素的pre-link函数能够保证是运行在它所有的子指令的post-link与pre-link运行之前执行的.见下图:

angularjs指令中的compile与link函数详解

回顾

如果我们回头看看上面原始的输出,就能清楚的认出到底发生了什么:

    // HERE THE ELEMENTS ARE STILL THE ORIGINAL TEMPLATE ELEMENTS
    // COMPILE PHASE

    // levelOne:    compile function is called on original DOM

    // levelTwo:    compile function is called on original DOM

    // levelThree:  compile function is called on original DOM
    // AS OF HERE, THE ELEMENTS HAVE BEEN INSTANTIATED AND

    // ARE BOUND TO A SCOPE

    // (E.G. NG-REPEAT WOULD HAVE MULTIPLE INSTANCES)
    // PRE-LINK PHASE

    // levelOne:    pre link function is called on element instance

    // levelTwo:    pre link function is called on element instance

    // levelThree:  pre link function is called on element instance
    // POST-LINK PHASE (Notice the reverse order)

    // levelThree:  post link function is called on element instance

    // levelTwo:    post link function is called on element instance

    // levelOne:    post link function is called on element instance

概要

回顾上面的分析我们可以描述一下这些函数的区别以及使用情况:

Compile 函数

使用compile函数可以改变原始的dom(template element),在ng创建原始dom实例以及创建scope实例之前.

可以应用于当需要生成多个element实例,只有一个template element的情况,ng-repeat就是一个最好的例子,它就在是compile函数阶段改变原始的dom生成多个原始dom节点,然后每个又生成element实例.因为compile只会运行一次,所以当你需要生成多个element实例的时候是可以提高性能的.

template element以及相关的属性是做为参数传递给compile函数的,不过这时候scope是不能用的:

下面是函数样子:

/**

    * Compile function

    * 

    * @param tElem - template element

    * @param tAttrs - attributes of the template element

    */

    function(tElem, tAttrs){
        // ...
    };

Pre-link 函数

使用pre-link函数可以运行一些业务代码在ng执行完compile函数之后,但是在它所有子指令的post-link函数将要执行之前.

scope对象以及element实例将会做为参数传递给pre-link函数:

下面是函数样子:

/**

    * Pre-link function

    * 

    * @param scope - scope associated with this istance

    * @param iElem - instance element

    * @param iAttrs - attributes of the instance element

    */

    function(scope, iElem, iAttrs){
        // ...
    };

Post-link 函数

使用post-link函数来执行业务逻辑,在这个阶段,它已经知道它所有的子指令已经编译完成并且pre-link以及post-link函数已经执行完成.

这就是被认为是最安全以及默认的编写业务逻辑代码的原因.

scope实例以及element实例做为参数传递给post-link函数:

下面是函数样子:

/**

    * Post-link function

    * 

    * @param scope - scope associated with this istance

    * @param iElem - instance element

    * @param iAttrs - attributes of the instance element

    */

    function(scope, iElem, iAttrs){
        // ...
    };

总结

现在你应该对compile,pre-link,post-link这此函数之间的区别有了清晰的认识了吧.

如果还没有的话,并且你是一个认真的ng开发者,那么我强烈建议你重新把这篇文章读一读直到你了解为止

理解这些概念非常重要,能够帮助你理解ng原生指令的工作原理,也能帮你优化你自己的自定义指令.

如果还有问题的话,欢迎大家在下面评论里加上你的问题

以后还会接着分析关于指令里的其它两个问题:

1.指令使用transclusion属性是怎么工作的?
2.指令的controller函数是怎么关联的

最后,如果发现本文哪里有不对的,请及时给我发评论

谢谢!

Javascript 相关文章推荐
WEB 浏览器兼容 推荐收藏
May 14 Javascript
javascript中对变量类型的判断方法
Aug 09 Javascript
简单实现js选项卡切换效果
Feb 03 Javascript
javascript每日必学之多态
Feb 23 Javascript
javascript动态获取登录时间和在线时长
Feb 25 Javascript
网页中JS函数自动执行常用三种方法
Mar 30 Javascript
详解JS构造函数中this和return
Sep 16 Javascript
JavaScript实现删除数组重复元素的5种常用高效算法总结
Jan 18 Javascript
微信小程序模版渲染详解
Jan 26 Javascript
Layui组件Table绑定行点击事件和获取行数据的方法
Aug 19 Javascript
一文读懂vue动态属性数据绑定(v-bind指令)
Jul 20 Javascript
再也不怕 JavaScript 报错了,怎么看怎么处理都在这儿
Dec 09 Javascript
angularjs的一些优化小技巧
Dec 06 #Javascript
JavaScript开发人员的10个关键习惯小结
Dec 05 #Javascript
node.js中RPC(远程过程调用)的实现原理介绍
Dec 05 #Javascript
node.js中实现同步操作的3种实现方法
Dec 05 #Javascript
node.js实现BigPipe详解
Dec 05 #Javascript
js实现点击添加一个input节点
Dec 05 #Javascript
Node.js实现的简易网页抓取功能示例
Dec 05 #Javascript
You might like
php实现的遍历文件夹下所有文件,编辑删除
2010/01/05 PHP
JavaScript 继承详解 第一篇
2009/08/30 Javascript
js中opener与parent的区别详细解析
2014/01/14 Javascript
javascript:void(0)的问题使用探讨
2014/04/10 Javascript
JS点击链接后慢慢展开隐藏着图片的方法
2015/02/17 Javascript
省市选择的简单实现(基于zepto.js)
2016/06/21 Javascript
js实现为a标签添加事件的方法(使用闭包循环)
2016/08/02 Javascript
jQuery实现可移动选项的左右下拉列表示例
2016/12/26 Javascript
解决浏览器会自动填充密码的问题
2017/04/28 Javascript
vue 打包后的文件部署到express服务器上的方法
2017/08/09 Javascript
vue+Java后端进行调试时解决跨域问题的方式
2017/10/19 Javascript
AngularJS使用ng-repeat遍历二维数组元素的方法详解
2017/11/11 Javascript
微信小程序发布新版本时自动提示用户更新的方法
2019/06/07 Javascript
[59:26]DOTA2上海特级锦标赛D组资格赛#1 EG VS VP第二局
2016/02/28 DOTA
详解Python 数据库的Connection、Cursor两大对象
2018/06/25 Python
python实现简单名片管理系统
2018/11/30 Python
python调用c++ ctype list传数组或者返回数组的方法
2019/02/13 Python
python opencv实现图像边缘检测
2019/04/29 Python
Python Django 命名空间模式的实现
2019/08/09 Python
Python3 实现减少可调用对象的参数个数
2019/12/20 Python
python实现扫雷游戏
2020/03/03 Python
基于python实现破解滑动验证码过程解析
2020/05/28 Python
Python3 webservice接口测试代码详解
2020/06/23 Python
Scrapy模拟登录赶集网的实现代码
2020/07/07 Python
Python使用内置函数setattr设置对象的属性值
2020/10/16 Python
Python实现对word文档添加密码去除密码的示例代码
2020/12/29 Python
使用简单的CSS3属性实现炫酷读者墙效果
2014/01/08 HTML / CSS
使用HTML5做个画图板的方法介绍
2013/05/03 HTML / CSS
拉斯维加斯酒店、演出、旅游、俱乐部及更多:Vegas.com
2019/02/28 全球购物
您在慕尼黑的跑步商店:Lauf-bar
2019/10/11 全球购物
经济管理专业毕业生推荐信
2013/11/11 职场文书
放飞中国梦演讲稿
2014/04/23 职场文书
学习优秀党务工作者先进事迹材料思想报告
2014/09/17 职场文书
离婚协议书范本及离婚须知
2014/10/15 职场文书
2016年大学生就业指导课心得体会
2015/10/09 职场文书
SQL Server数据库基本概念、组成、常用对象与约束
2022/03/20 SQL Server