我剖析了ng-repeat并提取了附加的代码块,看到它们包含处理重复算法(我想了解其工作原理)的逻辑。
我有很多问题,但是由于它们都是有关ng-repeat的内部信息,因此我选择在这里全部询问。我看不出有任何理由将它们分为不同的SO问题。我已标记内联每个问题所指向的代码行。
trackById
不是本地的hasOwnProperty
函数? (那是assertNotHasOwnProperty
函数的工作,是Angular内部API的一部分)nextBlockMap
和都存储在nextBlockOrder
中? block.endNode
和block.startNode
? nextNode
是否具有(成为)'$$NG_REMOVED'
? 就像我说的那样,我通过ng-repeat进行了查找,找到了我认为与重复机制相关的代码。另外,我确实了解该指令的其余部分。因此,事不宜迟,下面是代码(来自v1.2.0):
length = nextBlockOrder.length = collectionKeys.length;
for (index = 0; index < length; index++) {
key = (collection === collectionKeys) ? index : collectionKeys[index];
value = collection[key];
trackById = trackByIdFn(key, value, index);
// question #1
assertNotHasOwnProperty(trackById, '`track by` id');
// question #2
if (lastBlockMap.hasOwnProperty(trackById)) {
block = lastBlockMap[trackById];
delete lastBlockMap[trackById];
nextBlockMap[trackById] = block;
nextBlockOrder[index] = block;
// question #3
} else if (nextBlockMap.hasOwnProperty(trackById)) {
// restore lastBlockMap
forEach(nextBlockOrder, function(block) {
if (block && block.startNode) lastBlockMap[block.id] = block;
});
// This is a duplicate and we need to throw an error
throw ngRepeatMinErr('dupes', "Duplicates in a repeater are not allowed. Use 'track by' expression to specify unique keys. Repeater: {0}, Duplicate key: {1}",
expression, trackById);
// question #4
} else {
// new never before seen block
nextBlockOrder[index] = { id: trackById };
nextBlockMap[trackById] = false;
}
}
for (index = 0, length = collectionKeys.length; index < length; index++) {
key = (collection === collectionKeys) ? index : collectionKeys[index];
value = collection[key];
block = nextBlockOrder[index];
// question #5
if (nextBlockOrder[index - 1]) previousNode = nextBlockOrder[index - 1].endNode;
if (block.startNode) {
// if we have already seen this object, then we need to reuse the
// associated scope/element
childScope = block.scope;
// question #6
nextNode = previousNode;
do {
nextNode = nextNode.nextSibling;
} while(nextNode && nextNode[NG_REMOVED]);
if (block.startNode != nextNode) {
// existing item which got moved
$animate.move(getBlockElements(block), null, jqLite(previousNode));
}
previousNode = block.endNode;
} else {
// new item which we don't know about
childScope = $scope.$new();
}
// question #7
if (!block.startNode) {
linker(childScope, function(clone) {
clone[clone.length++] = document.createComment(' end ngRepeat: ' + expression + ' ');
$animate.enter(clone, null, jqLite(previousNode));
previousNode = clone;
block.scope = childScope;
block.startNode = previousNode && previousNode.endNode ? previousNode.endNode : clone[0];
block.endNode = clone[clone.length - 1];
nextBlockMap[block.id] = block;
});
}
}
lastBlockMap = nextBlockMap;
最佳答案
在修改了该指令之后,我熟悉了ng-repeater
的代码,并设法回答了我的一些问题。我用粗体突出显示了我自己还无法解决的问题,如果有人可以对粗体零件有所了解,我将不胜感激:
hasOwnProperty
进行了测试,因为它们使用该方法检查ID是否存在于迭代对象(lastBlockMap
,nextBlockMap
)中(此过程在下面说明)。 但是,我无法确定在什么情况下会真正发生这种情况。 nextBlockMap
包含在当前模型更改时将被排除的所有项目。 lastBlockMap
包含先前模型更新中的所有内容。它用于在集合中查找重复项。 for
循环中,ng-repeat
用nextBlockMap
中的项目填充lastBlockMap
。查看if
的顺序,可以很容易地看到,如果在lastBlockMap
中找不到该项目,但是它已经存在于nextBlockMap
中(这意味着它已经从lastBlockMap
复制到了该项目,因此它的trackById
在集合中出现了两次) -它是重复的。 forEach
所做的只是简单地遍历nextBlockMap
(具有block
属性的startNode
)中的所有初始化项,然后将其ID推回到lastBlockMap
中。 但是我不明白为什么这样做是必要的。 nextBlockOrder
(数组中的所有trackById
)与nextBlockMap
(block
哈希中的所有trackById
对象)分开的唯一原因是这一行,它与数组一起使用使操作变得简单易行:if (nextBlockOrder[index - 1]) previousNode = nextBlockOrder[index - 1].endNode;
。在问题5和6的答案中对此进行了解释:block.startNode
和block.endNode
是块中第一个和最后一个DOM节点,属于所收集的重复项中的一项。因此,此行在此处设置previousNode
以引用转发器中上一项的最后一个DOM节点。 previousNode
用作第一个节点,该循环检查在将项目移入或从转发器集合中移除时DOM的变化-再次,仅在我们不使用数组中的第一个块的情况下。 $scope
和startNode
和endNode
供以后引用,并将所有内容保存在nextBlockMap
中。在克隆元素之后立即创建的注释可以确保我们始终有一个endNode
。