为什么 ng-repeat 改变 link 函数执行的顺序

why ng-repeat changes order of link function execution

嵌套指令上编译和link函数的通常执行顺序如下

标记

<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

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

为什么会这样?是因为 ng-repeat 已将 transclude 属性 设置为 element。如果是这样,为什么要更改 ng-repeat.

之外的 dir1 的执行顺序

如有任何帮助,我们将不胜感激。

首先,问得好!我以前用angular开发过好几个webapp,但我从来没有意识到这一点。

这是因为在 ngRepeat 实施中,google 团队使用 $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 语句,您将获得第一个示例的输出。您可以将 $watchCollection 替换为 setTimeout 以重现相同的日志。

如果我们查看 angular.js 的源代码,调用堆栈将类似于 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 你的示例的 angular 版本似乎是 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.