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


嵌套

指令上编译和链接函数的通常执行顺序如下

标记

<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-repeattransclude属性设置为 element 。如果是这样的话,为什么要改变dir1的执行顺序,这是ng-repeat之外的。

任何帮助将不胜感激。

首先,好问题!我曾经使用 angular 来开发几个 Web 应用程序,但我从未意识到这一点。

这是因为在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*([sS]+?)s+ins+([sS]+?)(?:s+ass+([sS]+?))?(?:s+tracks+bys+([sS]+?))?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源代码。

$browser.延迟源代码。

希望这能解决您的问题。

这是您的示例的分支,带有 myRepeat 实现。有关更多详细信息,您可以查看angular.js的github。

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


异步演示更新

有关设置超时的更多详细信息。

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

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

示例 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 的 JavaScript 计时器如何工作。

相关内容

  • 没有找到相关文章

最新更新