为什么 ng-repeat 会改变链接函数执行的顺序 [英] why ng-repeat changes order of link function execution

查看:33
本文介绍了为什么 ng-repeat 会改变链接函数执行的顺序的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

标记

<div dir2="">

</dir1>

执行顺序

1) 编译指令 12) 编译指令 23) 指令 2 的链接4) 指令 1 的链接

假设 dir1restrict 属性设置为 'E' 并且 dir2restrict 设置为 'A'

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

标记

<div ng-repeat="项目中的项目"><div dir2="">

</dir1>

假设在作用域上定义了items,执行顺序变为

1) 编译指令 12) 指令 1 的链接3) 编译指令 24) 指令 2 的链接

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

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

任何帮助将不胜感激.

解决方案

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

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

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

//mock ng-repeat :)app.directive('myRepeat', function ($compile) {返回 {限制:'A',嵌入:'元素',优先级:1000,终端:真的,$$tlb: 真的,编译:函数($element,$attr){var 表达式 = $attr.myRepeat;var ngRepeatEndComment = $compile.$$createComment('end myRepeat', expression);//解析ngRepeat表达式.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];//这将在您的示例中获取项目返回函数 ($scope, $element, $attr, ctrl, $transclude) {//$watch $scope[rhs] 其中 rhs 将是您的示例中的项目.$scope.$watchCollection(rhs, function myRepeatAction(collection) {$transclude(功能(克隆,范围){克隆[clone.length++] = 克隆;//追加元素});});}}}});

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

如果我们查看 angular.js 的源代码,调用堆栈将类似于 watchCollection =>$watch =>$evalAsync =>$browser.defer =>设置超时

$watch 源代码.

$browser.defer 源代码.

希望这能解决您的问题.:)

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

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

<小时>

异步演示更新

有关 setTimeout 的更多详细信息.

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

function dir1(callback) {console.log('编译目录1');打回来();console.log('link dir1');}函数 dir2() {console.log('编译目录2');console.log('link dir2');}目录 1(目录 2);//编译目录1//编译目录2//链接目录2//链接目录1

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

function dir1(callback) {console.log('编译目录1');打回来();console.log('link dir1');}函数 dir2() {console.log('编译目录2');console.log('link dir2');}功能 myRepeat(回调){返回函数(){设置超时(回调,0);}}dir1(myRepeat(dir2));//编译目录1//链接目录1//编译目录2//链接目录2

示例 2 的示例代码. 看起来很有趣,不是吗?

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

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

<代码>1.编译目录12.setTimeout(0秒后执行)3.链接dir1(当前块正在运行,所以先这样做)4.编译dir2(现在免费,调用回调)5.链接目录2

这就是我所说的异步.有关 setTimeout 的更多详细信息,您可以查看 John Resig 的 javascript 计时器的工作原理.

The usual order of execution of compile and link function on nested directives is as below

Markup

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

Order of execution

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

Assuming dir1 has restrict property set to 'E' and dir2 has restrict set to 'A'

Now if you use a ng-repeat directive in the same markup, the order of execution changes

Markup

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

assuming items is defined on scope, the order of execution changes to

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

Why does this happen? Is is because ng-repeat has transclude property set to element. If that is the case, why should it alter the order of execution of dir1 which is outside ng-repeat.

Any help would be much appreciated.

解决方案

First of all, nice question! I used to use angular to develop several webapps, but I never realized this.

This is because inside of ngRepeat implementation, the google team use the $scope.$watchCollection to watch the variables and update the element.(With some other optimizations.) With invoking the $watchCollection, it calls the setTimeout to eval the changes asynchronously.

Then you can write down your own version of ngRepeat. Let's call it 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
                  });

                });   
            }
        }
    }
});

If you comment out the watchCollection statement, you will get the output of your first example. And you can replace the $watchCollection with setTimeout to reproduce the same logs also.

If we look into the source code of angular.js, the callstack would be like watchCollection => $watch => $evalAsync => $browser.defer => setTimeout

$watch source code.

$browser.defer source code.

Hope this would solve your problem. : )

This is the fork of your example, with myRepeat implementation. For more detail, you can check the github of angular.js.

P.S Seems the angular version of your example is 1.5.3, so all the source code would be in 1.5.3.


update for async demo

More details about the setTimeout.

Basically you can regard your example as some functions below,

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

And after added the custom version of ngRepeat, the code would be,

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. Seems pretty funny, isn't it?

The callback in setTimeout would be invoked after specific seconds, (would be 0 in our case).

But the callback would not be invoked until the current block of code completes its execution, which means in our case will output the link dir1 first.

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

So that's what I mean asynchronously. For more details about setTimeout, you can check John Resig's How javascript timers work.

这篇关于为什么 ng-repeat 会改变链接函数执行的顺序的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
其他开发最新文章
热门教程
热门工具
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆