问题
I dissected ng-repeat and extracted the code blocks attached, seeing that these comprise the logic that handles the repeating algorithm (which I want to understand how it works).
I have quite a few questions, but since they are all about the internals of ng-repeat I chose to ask them all here. I don't see any reason to separate them into different SO questions. I have marked inline to which line(s) of code each question refers to.
- Why do they need to make sure that
trackByIdis not the nativehasOwnPropertyfunction? (that's what thatassertNotHasOwnPropertyfunction does, part of Angular's internal API) - As far as my intuition go, this code executes on items already in the repeater, when it has to update the collection - it just picks them up and pushes them into the list for processing, right?
- This code block obviously looks for duplicates in the repeater collection. But how exactly does it do that is beyond me. Please explain.
- Why does Angular have to store the block object both
nextBlockMapand innextBlockOrder? - What are
block.endNodeandblock.startNode? - I assume the answer to the above question will clarify how this algorithm work, but please explain why it has to check if the
nextNodehas (been)'$$NG_REMOVED'? - What happens here? Again, I assume question 6 will already provide an answer to this one. But still pointing that out.
Like I said, I dug through ng-repeat to find the code I deemed relevant to the repeating mechanism. Plus, I do understand the rest of the directive. So without further ado, here is the code (from 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;
回答1:
After some tinkering with the directive, I became familiar with ng-repeaters code, and managed to answer some of my questions. I highlighted in bold the things I couldn't yet figure out on my own, and would appreciate if anyone could shed some light on the bold parts:
- The ID is tested for
hasOwnProperty, because they use that method to check whether the ID is present in the iteration objects (lastBlockMap,nextBlockMap) (this process explained below). I couldn't find out on what scenario this can actually happen, however. - I was correct in my assumption.
nextBlockMapcontains all items that will be transcluded on the current model change.lastBlockMapcontains everything from the previous model update. It used for finding duplicates in the collection. - OK, this one is pretty straightforward actually. In this
forloop,ng-repeatfills upnextBlockMapwith items fromlastBlockMap. Looking at the order ofifs, it's easy to see that if the item cannot be found inlastBlockMap, but it is already present innextBlockMap(meaning, it was already copied there fromlastBlockMap, and therefore itstrackByIdappears twice in the collection) - it's a duplicate. What theforEachdoes is simply run through all initialized items innextBlockMap(blocks that have astartNodeproperty) and push their ID back intolastBlockMap. I cannot however understand why this is necessary. - The only reason I could find for separating
nextBlockOrder(alltrackByIds in an array) fromnextBlockMap(allblockobjects in atrackByIdhash), is this line, which working with an array makes it an easy and simple operation:if (nextBlockOrder[index - 1]) previousNode = nextBlockOrder[index - 1].endNode;. It is explained in the answers to question 5 and 6: block.startNodeandblock.endNodeare the first and last DOM nodes in the block belonging to an item in the collected being repeated. Therefore, this line here setspreviousNodeto reference the last DOM node of the previous item in the repeater.previousNodeis then used as the first node, in a loop that checks how the DOM changed when items have been moved around or removed from the repeater collection - again, only in case we are not working with the first block in the array.- This is easy - it initializes the block - assigning the
$scopeandstartNodeandendNodefor later reference, and saves everything innextBlockMap. The comment created right after the cloned element, is there to guarantee we always have anendNode.
来源:https://stackoverflow.com/questions/20133429/how-does-ng-repeat-work