指令上编译和链接函数的通常执行顺序如下
标记
<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
。如果是这样的话,为什么要改变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 计时器如何工作。