为什么ng-repeat改变了链接函数执行的顺序

时间:2016-05-02 04:51:34

标签: angularjs angularjs-directive angularjs-ng-repeat angularjs-compile

嵌套指令的编译和链接函数的通常执行顺序如下所示

标记

<dir1>
  <div dir2="">
  </div>
</dir1>

执行顺序

1) compile of directive 1
2) compile of directive 2
3) link of directive 2
4) link of directive 1

假设dir1 restrict属性设置为'E'dir2 restrict设置为'A'

现在,如果在同一标记中使用ng-repeat指令,则执行顺序会发生变化

标记

<dir1>
  <div ng-repeat="item in items">
    <div dir2="">
    </div>
  </div>
</dir1>

假设在范围上定义items,执行顺序将更改为

1) compile of directive 1
2) link of directive 1
3) compile of directive 2
4) link of directive 2

Plunker - https://plnkr.co/edit/fRGHS1Bqu3rrY5NW2d97?p=preview

为什么会这样?是因为ng-repeattransclude属性设置为element。如果是这种情况,为什么要改变dir1之外ng-repeat的执行顺序。

非常感谢任何帮助。

1 个答案:

答案 0 :(得分:5)

首先,好问题!我曾经使用angular来开发几个webapps,但我从来没有意识到这一点。

这是因为在ngRepeat实施中,谷歌团队使用了 $scope.$watchCollection观察变量并更新元素。(使用其他一些优化。)通过调用$ watchCollection,它调用setTimeout来异步评估更改。

然后你可以写下你自己的ngRepeat版本。我们称之为myRepeat

//mock ng-repeat : )
app.directive('myRepeat', function ($compile) {
    return {
        restrict:'A',
        transclude: 'element',
        priority: 1000,
        terminal: true,
        $$tlb: true,
        compile: function ($element, $attr) {
            var expression = $attr.myRepeat;
            var ngRepeatEndComment = $compile.$$createComment('end myRepeat', expression);

            //parse the ngRepeat expressions.
            var match = expression.match(/^\s*([\s\S]+?)\s+in\s+([\s\S]+?)(?:\s+as\s+([\s\S]+?))?(?:\s+track\s+by\s+([\s\S]+?))?\s*$/);


            var rhs = match[2]; //this would get items in your example

            return function ($scope, $element, $attr, ctrl, $transclude) {

                //$watch $scope[rhs] which rhs would be items in your example.

                $scope.$watchCollection(rhs, function myRepeatAction(collection) {
                  $transclude(function(clone, scope) {

                    clone[clone.length++] = clone; //append element
                  });

                });   
            }
        }
    }
});

如果您注释掉watchCollection语句,您将获得第一个示例的输出。你可以用setTimeout替换$ watchCollection来重现相同的日志。

如果我们查看angular.js的源代码,那么callstack就像watchCollection => $watch => $evalAsync => $browser.defer => setTimeout

$watch source code.

$browser.defer source code.

希望这可以解决您的问题。 :)

This is the fork of your example, with myRepeat implementation. 有关详细信息,请查看github of angular.js

P.S似乎您的示例的角度版本是1.5.3,所以所有源代码都在 1.5.3。

更新异步演示

有关setTimeout的更多详细信息。

基本上你可以将你的例子看作下面的一些函数,

function dir1(callback) {

   console.log('compile dir1');
   callback();
   console.log('link dir1');
}

function dir2() {
   console.log('compile dir2');
   console.log('link dir2');
}

dir1(dir2);
//compile dir1
//compile dir2
//link dir2
//link dir1

添加ngRepeat的自定义版本后,代码为

function dir1(callback) {
   console.log('compile dir1');
   callback();
   console.log('link dir1');
}

function dir2() {
   console.log('compile dir2');
   console.log('link dir2');
}
function myRepeat(callback) {
   return function() {
       setTimeout(callback, 0);
   }
}

dir1(myRepeat(dir2));
//compile dir1
//link dir1
//compile dir2
//link dir2

Sample Code for example 2.看起来很有趣,不是吗?

setTimeout中的回调将在特定秒后调用(在我们的例子中将为0)。

但是在当前代码块完成执行之前不会调用回调,这意味着在我们的例子中将首先输出link dir1

1. compile dir1
2. setTimeout(execute after 0 second)
3. link dir1(current block is running, so do this first) 
4. compile dir2 (it's free now, invoke the callback)
5. link dir2

这就是我异步的意思。 有关setTimeout的更多详细信息,您可以查看John Resig的How javascript timers work.