我剖析了ng-repeat并提取了附加的代码块,看到它们包含处理重复算法(我想了解其工作原理)的逻辑。

我有很多问题,但是由于它们都是有关ng-repeat的内部信息,因此我选择在这里全部询问。我看不出有任何理由将它们分为不同的SO问题。我已标记内联每个问题所指向的代码行。

  • 他们为什么需要确保trackById不是本地的hasOwnProperty函数? (那是assertNotHasOwnProperty函数的工作,是Angular内部API的一部分)
  • 就我的直觉而言,此代码在转发器中已有的项目上执行,当它必须更新集合时-它只是将它们拾取并将其插入列表进行处理,对吗?
  • 该代码块显然在转发器集合中查找重复项。但是,如何做到这一点超出了我的范围。请解释。
  • 为什么Angular必须将块对象nextBlockMap 都存储在nextBlockOrder中?
  • 什么是block.endNodeblock.startNode
  • 我认为上述问题的答案将阐明该算法的工作原理,但请解释为什么它必须检查nextNode是否具有(成为)'$$NG_REMOVED'
  • 这会发生什么?再次,我认为问题6将已经为这一问题提供了答案。但仍要指出这一点。

  • 就像我说的那样,我通过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的代码,并设法回答了我的一些问题。我用粗体突出显示了我自己还无法解决的问题,如果有人可以对粗体零件有所了解,我将不胜感激:

  • 该ID已针对hasOwnProperty进行了测试,因为它们使用该方法检查ID是否存在于迭代对象(lastBlockMapnextBlockMap)中(此过程在下面说明)。 但是,我无法确定在什么情况下会真正发生这种情况。
  • 我的假设是正确的。 nextBlockMap包含在当前模型更改时将被排除的所有项目。 lastBlockMap包含先前模型更新中的所有内容。它用于在集合中查找重复项。
  • 好吧,这实际上很简单。在此for循环中,ng-repeatnextBlockMap中的项目填充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.startNodeblock.endNode是块中第一个和最后一个DOM节点,属于所收集的重复项中的一项。因此,此行在此处设置previousNode以引用转发器中上一项的最后一个DOM节点。
  • 然后,在循环中,将
  • previousNode用作第一个节点,该循环检查在将项目移入或从转发器集合中移除时DOM的变化-再次,仅在我们不使用数组中的第一个块的情况下。
  • 这很容易-初始化块-分配$scopestartNodeendNode供以后引用,并将所有内容保存在nextBlockMap中。在克隆元素之后立即创建的注释可以确保我们始终有一个endNode
  • 10-08 14:31