【问题标题】:Unique element ID, even if element doesn't have one唯一的元素 ID,即使元素没有
【发布时间】:2010-09-18 14:35:22
【问题描述】:

我正在编写一个 GreaseMonkey 脚本,在其中迭代一堆元素。对于每个元素,我需要一个字符串 ID,以后可以使用它来引用该元素。元素本身没有id 属性,我无法修改原始文档以赋予它一个(尽管我可以在我的脚本中进行DOM 更改)。我无法将引用存储在我的脚本中,因为当我需要它们时,GreaseMonkey 脚本本身就会超出范围。例如,有什么方法可以获取浏览器使用的“内部”ID?仅 Firefox 的解决方案很好;可以应用于其他场景的跨浏览器解决方案会很棒。

编辑:

  • 如果 GreaseMonkey 脚本超出范围,您以后如何引用这些元素? GreaseMonkey 脚本正在向 DOM 对象添加事件。我无法将引用存储在数组或其他类似机制中,因为当事件触发时,数组将消失,因为 GreaseMonkey 脚本将超出范围。因此,事件需要某种方式来了解附加事件时脚本所具有的元素引用。而且有问题的元素不是它所附加的元素。

  • 您不能只在元素上使用自定义属性吗? 可以,但问题出在查找上。我不得不求助于遍历所有元素,以寻找将自定义属性设置为所需 id 的元素。这当然可以,但在大型文档中可能非常耗时。我正在寻找浏览器可以进行查找繁重工作的东西。

  • 等等,你能不能修改文档?我不能修改源文档,但我可以在脚本中进行 DOM 更改。我会在问题中澄清。

  • 你不能使用闭包吗? 闭包确实有效,尽管我最初认为它们不会。看我后面的帖子。

这听起来像是问题的答案:“我可以使用一些内部浏览器 ID 吗?”是“不”。

【问题讨论】:

  • 非常有趣的问题。我实际上想知道各种浏览器如何实现这一点。也许有些浏览器为所有元素分配了一个隐藏索引,这样他们就可以在内部访问它们而无需遍历整个 DOM 树。这是有道理的。问题是我们是否可以从 JavaScript 访问索引。
  • @AsGoodAsItGets 实际上,这是相反的,因为这个问题是首先创建的。 :)
  • @AsGoodAsItGets: 耸耸肩 没有人会强迫你这样做;如果你觉得你应该这样做。
  • 在数据库设计中,我们通常会创建一个唯一的 ID 字段来通过一个保证永远不会更改的数字来查找记录(行),即使我们主要通过另一个字段进行查找,为了可扩展性。 DOM 设计者不会将唯一的整数放入创建的每个节点,或者至少每个元素类型的节点中,这似乎很奇怪。另一方面,Node 对象只是散布着函数值,这些函数值可能存储在静态类或实际对象之外的其他地方。只是说。

标签: javascript firefox dom greasemonkey


【解决方案1】:

您可以使用以下函数为 DOM 中的任何给定节点生成稳定的唯一标识符:

function getUniqueKeyForNode (targetNode) {
    const pieces = ['doc'];
    let node = targetNode;

    while (node && node.parentNode) {
        pieces.push(Array.prototype.indexOf.call(node.parentNode.childNodes, node));
        node = node.parentNode
    }

    return pieces.reverse().join('/');
}

这将为这样的结构创建标识符,例如 doc/0doc/0/0doc/0/1doc/0/1/0doc/0/1/1

<div>
    <div />
    <div>
        <div />
        <div />
    </div>
</div>

您还可以进行一些优化和更改,例如:

  • while 循环中,breaknode 具有您知道是唯一的属性时,例如@id

  • 不是reverse()pieces,目前只是看起来更像是生成ID 的DOM 结构

  • 如果您不需要文档节点的标识符,则不包括第一个 piece doc

  • 以某种方式保存节点上的标识符,并为子节点重复使用该值,以避免再次遍历树。

  • 如果您将这些标识符写回 XML,如果您正在写入的属性受到限制,请使用另一个连接字符。

【讨论】:

    【解决方案2】:

    好的,没有自动关联到 DOM 元素的 ID。 DOM 具有元素的层次结构,这是主要信息。 从这个角度来看,您可以使用 jQuery 或 jQLite 将数据关联到 DOM 元素。当您必须将自定义数据绑定到元素时,它可以解决一些问题。

    【讨论】:

      【解决方案3】:

      我“认为”我刚刚解决了一个类似的问题。但是,我在浏览器 DOM 环境中使用 jQuery。

      var objA = $("选择器到某个 dom 元素"); var objB = $("其他 dom 元素的选择器");

      如果(objA[0] === objB[0]){ //伟大的!这两个对象指向完全相同的 dom 节点 }

      【讨论】:

        【解决方案4】:

        使用元素的鼠标和/或位置属性生成唯一 ID。

        【讨论】:

        • 如果元素完全重叠,这并不是完全的证明
        【解决方案5】:

        您还可以使用 pguid(页面唯一标识符)生成唯一标识符:

         pguid = b9j.pguid.next() // A unique id (suitable for a DOM element)
                                  // is generated
                                  // Something like "b9j-pguid-20a9ff-0"
         ...
         pguid = b9j.pguid.next() // Another unique one... "b9j-pguid-20a9ff-1"
        
         // Build a custom generator
         var sequence = new b9j.pguid.Sequence({ namespace: "frobozz" })
         pguid = sequence.next() "frobozz-c861e1-0"
        

        http://appengine.bravo9.com/b9j/documentation/pguid.html

        【讨论】:

          【解决方案6】:

          更新:闭包确实是答案。因此,在又摆弄了一些之后,我弄清楚了为什么闭包最初是有问题的以及如何解决它。闭包的棘手之处在于,在遍历元素时必须小心,不要让所有闭包都引用同一个元素。例如,这不起作用:

          for (var i = 0; i < elements.length; i++) {
              var element = elements[i];
              var button = document.createElement("button");
              button.addEventListener("click", function(ev) {
                  // do something with element here
              }, false)
          }
          

          但这确实:

          var buildListener = function(element) {
              return function(ev) {
                  // do something with event here
              };
          };
          
          for (var i = 0; i < elements.length; i++) {
              var element = elements[i];
              var button = document.createElement("button");
              button.addEventListener("click", buildListener(element), false)
          }
          

          无论如何,我决定不选择一个答案,因为该问题有两个答案:1) 不,没有可以使用的内部 ID; 2)你应该为此使用闭包。所以我只是简单地支持第一批说出是否有内部 ID 或建议生成 ID 的人,以及任何提到关闭的人。感谢您的帮助!

          【讨论】:

          【解决方案7】:

          对您的问题的措辞有点困惑 - 您说您“需要一个 [您] 以后可以用来引用该元素的字符串 ID”,但是您“不能将引用存储在 [您的] 脚本中因为当 [你] 需要它们时,GreaseMonkey 脚本本身就会超出范围。”

          如果脚本超出范围,那么您以后如何引用它们?!

          我将忽略我对你的理解感到困惑的事实,并告诉你我经常编写 Greasemonkey 脚本并且可以修改我访问的 DOM 元素以给它们一个 ID财产。这是您可以用来获取伪唯一值以供临时使用的代码:

          var PseudoGuid = new (function() {
              this.empty = "00000000-0000-0000-0000-000000000000";
              this.GetNew = function() {
                  var fourChars = function() {
                      return (((1 + Math.random()) * 0x10000)|0).toString(16).substring(1).toUpperCase();
                  }
                  return (fourChars() + fourChars() + "-" + fourChars() + "-" + fourChars() + "-" + fourChars() + "-" + fourChars() + fourChars() + fourChars());
              };
          })();
          
          // usage example:
          var tempId = PseudoGuid.GetNew();
          someDomElement.id = tempId;
          

          这对我有用,我只是自己在 Greasemonkey 脚本中测试过。


          更新:闭包是必经之路 - 就个人而言,作为一名核心 JavaScript 开发人员,我不知道您是如何没有立即想到这些的. :)

          myDomElement; // some DOM element we want later reference to
          
          someOtherDomElement.addEventListener("click", function(e) {
             // because of the closure, here we have a reference to myDomElement
             doSomething(myDomElement);
          }, false);
          

          现在,myDomElement 显然是根据您的描述,您已经拥有的元素之一(因为您正在考虑为其添加 ID 或其他)。

          如果你发布一个你正在尝试做的事情的例子,假设这不会更容易帮助你。

          【讨论】:

          • 伟大的思想都一样。 Nitpick:DOM id 必须以 A-Z 字符开头,因此这会破坏 HTML(虽然有效)。 :-)
          • 是的 - 我倾向于忘记规范的一些细节,因为大多数浏览器都非常方便。 :) 我在自己的 JavaScript 代码中使用这种模式已经有一段时间了,没有问题,但既然你提到了它,我想我会做出改变以确保有效性......也许吧。 :P
          • 是的,我通常会在这样的场景中使用闭包。我不知道为什么在这种情况下我没有想到它们。我把它归咎于太多的晚上工作到很晚。 :)
          • DOM 元素上的闭包不会导致浏览器泄漏,至少在 IE 中是这样吗?
          • @ErikE - 据我记得,而且它比我愿意承认的要长,当您将事件处理程序附加到从 DOM 中删除且不再存在的 DOM 元素时,就会发生内存泄漏问题被任何变量引用。
          【解决方案8】:

          关闭是要走的路。这样,您就可以准确地引用元素,甚至可以在 DOM 的一些洗牌中幸存下来。

          不知道闭包的例子:

          var saved_element = findThatDOMNode();
          
          document.body.onclick = function() 
          {
             alert(saved_element); // it's still there!
          }
          

          如果您必须将其存储在 cookie 中,那么我建议为它计算 XPath(例如,遍历 DOM 计算以前的兄弟姐妹,直到找到具有 ID 的元素,您最终会得到类似[@id=foo]/div[4]/p[2]/a)。

          XPointer 是 W3C 针对该问题的解决方案。

          【讨论】:

            【解决方案9】:

            如果您可以写入 DOM(我相信您可以)。我会这样解决这个问题:

            有一个函数返回或生成一个 ID:

            //(function () {
            
              var idCounter = new Date().getTime();
              function getId( node ) {
                return (node.id) ? node.id : (node.id = 'tempIdPrefix_' + idCounter++ );
              }
            
            //})();
            

            根据需要使用它来获取 ID:

            var n = document.getElementById('someid');
            getId(n);  // returns "someid"
            
            var n = document.getElementsByTagName('div')[1];
            getId(n);  // returns "tempIdPrefix_1224697942198"
            

            这样您就不必担心服务器将 HTML 交给您时的样子。

            【讨论】:

              【解决方案10】:

              您可以将 id 属性设置为计算值。原型库中有一个函数可以为你做到这一点。

              http://www.prototypejs.org/api/element/identify

              我最喜欢的 javascript 库是 jQuery。不幸的是,jQuery 没有识别功能。但是,您仍然可以将 id 属性设置为您自己生成的值。

              http://docs.jquery.com/Attributes/attr#keyfn

              这是来自 jQuery 文档的部分 sn-p,它根据页面中的位置为 div 设置 id:

                $(document).ready(function(){
              
                  $("div").attr("id", function (arr) {
                        return "div-id" + arr;
                      });
                });
              

              【讨论】:

                【解决方案11】:

                如果您不修改 DOM,则可以通过索引顺序获取它们:

                Prototype 示例)

                myNodes = document.body.descendants()
                alert(document.body.descendants()[1].innerHTML)
                

                您可以遍历所有节点并为它们指定一个唯一的类名,以便以后轻松选择。

                【讨论】:

                • 这种由遍历 DOM 生成的唯一名称将在 HTML 文件更改时更改(仅删除一个节点可能会更改许多分配的名称或标识符)。因此,持久 id 显然只能由 HTML 文件的作者分配! HTML5 没有解决的问题。
                【解决方案12】:

                答案是否定的,没有您可以访问的内部 ID。 Opera 和 IE(也许是 Safari?)支持 .sourceIndex(如果 DOM 支持,则会更改),但 Firefox 没有这种类型。

                您可以通过生成给定节点的 Xpath 或从 document.getElementsByTagName('*') 查找节点的索引来模拟源索引,这将始终按源顺序返回元素。

                当然,所有这些都需要一个完全静态的文件。对 DOM 的更改会破坏查找。

                我不明白的是,您如何可以松散对节点的引用,而不是对(理论上的)内部 id 的引用?闭包和分配工作或不工作。还是我错过了什么?

                【讨论】:

                  【解决方案13】:

                  在 javascript 中,您可以将自定义 ID 字段附加到节点

                  if(node.id) {
                    node.myId = node.id;
                  } else {
                    node.myId = createId();
                  }
                  
                  // store myId
                  

                  这有点小技巧,但它会给每个节点一个你可以使用的 id。当然document.getElementById()不会理会。​​p>

                  【讨论】:

                  • 是的,但问题出在稍后的查找端。我必须再次找到该元素的唯一方法是迭代文档中的所有元素并比较该自定义属性。根据文档大小,这可能会很慢。
                  猜你喜欢
                  • 2015-05-15
                  • 2015-08-31
                  • 1970-01-01
                  • 2017-08-22
                  • 1970-01-01
                  • 2015-11-28
                  • 1970-01-01
                  • 1970-01-01
                  • 2023-03-20
                  相关资源
                  最近更新 更多