根据 DOM 元素在 DOM 中的位置对包含 DOM 元素的数组进行排序

Posted

技术标签:

【中文标题】根据 DOM 元素在 DOM 中的位置对包含 DOM 元素的数组进行排序【英文标题】:Sort array containing DOM elements according to their position in the DOM 【发布时间】:2014-05-02 00:14:22 【问题描述】:

上下文

我已经构建了一个我目前正在使用的 jQuery 插件,它让我将 DOM 元素存储在一个数组中,主要是为了能够在这些元素旁边存储更多信息,而无需使用不那么-快data().

这个数组看起来像:

[
     element: DOMElement3, additionalData1: …, additionalData2: … ,
     element: DOMElement1, additionalData1: …, additionalData2: … ,
     element: DOMElement2, additionalData1: …, additionalData2: … ,
]

这个插件的工作方式使我无法以可预测的顺序将这些元素推送到数组中,这意味着 DOMElement3 实际上可以找到低于 DOMElement2 的索引。

但是,我需要按照与它们包含的 DOM 元素出现在 DOM 中相同的顺序对这些数组元素进行排序。前面的示例数组,一旦排序,将如下所示:

[
     element: DOMElement1, additionalData1: …, additionalData2: … ,
     element: DOMElement2, additionalData1: …, additionalData2: … ,
     element: DOMElement3, additionalData1: …, additionalData2: … ,
]

当然,如果 DOMElement1 出现在 DOM 中的 DOMElement2 之前,DOMElement2 出现在 DOMElement3 之前。

废弃的解决方案

jQuery add() 方法返回一组 DOM 元素,其顺序与它们在 DOM 中出现的顺序相同。我可以使用它,但要求是我使用 jQuery 集合——这意味着我必须重构上述插件的一大块以使用不同的存储格式。这就是为什么我认为这是最后的解决方案。

另一种解决方案?

我原以为map() 和一种与每个 DOM 元素相关联的全局 DOM 索引可以解决问题,但似乎没有这样的“全局 DOM 索引”。

你能想到什么方法? (如果编写代码,欢迎使用 vanilla JS 和 jQuery。)

【问题讨论】:

我很好奇您是否将您的方法与data 进行了基准测试,看看您的方法有多快。 “这个插件的工作方式使我无法以可预测的顺序将这些元素推送到数组中” 怎么样?看起来你应该改正它 @JamesMontagne 我没有,但是将元素推送到数组 似乎 比使用 jQuery 的内部缓存将附加数据附加到每个元素要快。我将创建一个 JSperf 来立即对其进行基准测试并在此处发布。 @A.Wolff 该插件允许用户随时向该数组添加元素。因此,如果仍然使用相同的示例,有人首先将DOMElement3 添加到数组中,然后再添加DOMElement1DOMElement2,则数组看起来与第一个代码示例中的完全相同。 @JamesMontagne 看起来我的方法确实比使用data() 更快,至少在以下测试用例中:jsperf.com/storing-data-next-to-dom-elements 【参考方案1】:

有一个非常有用的函数叫做compareDocumentPosition,它根据两个元素的相对位置返回一个数字。您可以在 .sort 回调中使用它:

yourArray.sort(function(a,b) 
    if( a === b) return 0;
    if( !a.compareDocumentPosition) 
        // support for IE8 and below
        return a.sourceIndex - b.sourceIndex;
    
    if( a.compareDocumentPosition(b) & 2) 
        // b comes before a
        return 1;
    
    return -1;
);

【讨论】:

这就是让我后脑发痒的东西。规范参考:w3.org/TR/DOM-Level-3-Core/…(不过,我喜欢它完全无法清楚地说明返回值是什么。) @T.J.Crowder 公平点,很容易添加,现在就可以了。 遗憾的是,IE8 仍然是一个重要的野生浏览器,并且没有 compareDocumentPosition。你必须使用contains。更多内容在this post from John Resig. 这里是我测试过的三个解决方案,在它们各自的小提琴中:compareDocumentPosition(@NiettheDarkAbsol 的答案)、remapping with expando(@TJCrowder 的答案)和document.all indexes(另一个尝试我给了那个问题)。最后,这是比较所有三个的 jsPerf:jsperf.com/sort-array-containing-dom-elements-according -举手-我赢了!以山体滑坡!哇!【参考方案2】:

虽然我不建议您采用这种方法,但使用 jquery 可以获得“全局 DOM 索引”:

var $all = $("*");

// use index to get your element's index within the entire DOM
$all.index($YOUR_EL);

【讨论】:

哈哈,确实可行,谢谢,最后的解决方案#2!【参考方案3】:

您无需进行大量重构即可利用 jQuery 的排序功能。您可以将其用作临时排序机制。这是一个现成的:

function putInDocumentOrder(a) 
      var elements;

    // Get elements in order and remember the index of
    // the entry in `a`
    elements = $().add(a.map(function(entry, index)
        entry.element.__index = index;
        return entry.element;
    ));

    // Build array of entries in element order
    a = elements.map(function()
        return a[this.__index];
    ).get();

    return a;

它需要一个expando,但它可以完成这项工作。 Live Example

这样做的好处是它可以在 jQuery 支持的所有浏览器上运行,而无需您自己处理边缘情况(例如,IE8 不支持compareDocumentPosition)。

【讨论】:

您确定最后一条语句吗?我不相信将 dom 元素数组传递给 jquery 构造函数会按 dom 顺序对它们进行排序。 @JamesMontagne:您正在查看一个旧页面。 :-) 我确定那是真的,然后想……我最好测试一下……所以我在测试时删除了它,刚刚再次编辑说不,那不是真的。跨度> 此解决方案适用于add()$("div").add([DOMElement3, DOMElement2, DOMElement1]) 将提供以下 jQuery 集合:[DOMElement1, DOMElement2, DOMElement3]。但是,我看不出这与我所写的“废弃解决方案”有何不同。 @user1728278:您说您想使用数组,而不是 jQuery 集合。不同之处在于最终使用get() 得到一个数组,而不是jQuery 集合。 @user1728278 最后,您可能最终需要排序集合和数据缓存之间的某种映射。这就是我第一个问题背后的想法,即你是否对它进行了基准测试。如果最终您必须完全重新创建data 的功能,您可能应该只使用data【参考方案4】:

如果您可以假设页面上的可见位置相同,那可能是一个相对快速的解决方案:

function compareByVisibleLocation(a, b) 
  if (a.offsetTop > b.offsetTop) 
    return 1;
   else if (a.offsetTop < b.offsetTop) 
    return -1;
  

  if (a.offsetLeft > b.offsetLeft) 
    return 1;
   else if (a.offsetLeft < b.offsetLeft) 
    return -1;
   else 
    return 0;
  


nodes.sort(compareByVisibleLocation);

否则,您可以使用querySelectorAll() 快速构建索引(这是深度优先的前序遍历),方法是使用indexed-node 类名显式标记要索引的节点。

var nodes = document.querySelectorAll('.indexed-node');

或者通过抓取所有单个节点类型,如果可行的话:

var nodes = document.querySelectorAll('div');

或者只是抓取所有节点:

var nodes = document.querySelectorAll('*');

然后给每个节点添加一个索引属性:

for (var i = 0; i < nodes.length; i++)  nodes[i].__DomIndex = i; 

你可以这样排序:

var nodes = yourGetMethod().sort(function(a,b) 
  if (a === b) 
    return 0;
   else if (a.__DomIndex > b.__DomIndex) 
    return 1;
   else 
    return -1;
  
);

应该工作到 IE8。

【讨论】:

视觉位置和 DOM 顺序通常是不相关的。浮标、各种定位等,都进来了。 @T.J.Crowder 当然,我并不反对。但是,如果 OP 的意图是按照页面上出现的顺序填充某些内容,那么这可能就是他所需要的......

以上是关于根据 DOM 元素在 DOM 中的位置对包含 DOM 元素的数组进行排序的主要内容,如果未能解决你的问题,请参考以下文章

jQuery之对象

DOM

JS中怎样取得DOM 元素位置

DOM中的节点属性

JavaScript 元素在DOM树中的绝对位置

web性能优化DOM的reflow 和repaint