在Jquery中获取元素的唯一选择器

Posted

技术标签:

【中文标题】在Jquery中获取元素的唯一选择器【英文标题】:Get unique selector of element in Jquery 【发布时间】:2011-08-08 02:07:57 【问题描述】:

我想创建一个类似记录器的东西,它可以跟踪用户的所有操作。为此,我需要识别用户与之交互的元素,以便我可以在以后的会话中引用这些元素。

用伪代码说话,我希望能够执行以下操作

示例 html(可能很复杂):

<html>
<body>
  <div class="example">
    <p>foo</p>
    <span><a href="bar">bar</a></span>
  </div>
</body>
</html>

用户点击了一些东西,比如链接。现在我需要识别被点击的元素并将其位置保存在 DOM 树中以供以后使用:

(any element).onclick(function() 
  uniqueSelector = $(this).getUniqueSelector();
)

现在,uniqueSelector 应该是这样的(我不介意它是 xpath 还是 css 选择器样式):

html > body > div.example > span > a

这将提供保存该选择器字符串并在以后使用它来重播用户所做的操作的可能性。

这怎么可能?

更新

得到我的答案:Getting a jQuery selector for an element

【问题讨论】:

对于 DOM 中的任何给定元素,无限数量的选择器是有效且唯一的。见***.com/questions/5135287/… 很想知道这个需求... @BoltClock:我不想拥有所有可能的选择器,只有其中一个是有效且唯一的。我不介意哪一个。 @Cyber​​nate:我正在使用 Selenium 2 与 Java 应用程序中的浏览器进行交互。它将 jQuery 加载到每个页面以具有进一步的交互可能性。 Getting a jQuery selector for an element 的可能副本 【参考方案1】:

我认为更好的解决方案是生成一个随机 id,然后根据该 id 访问一个元素:

分配唯一ID:

// or some other id-generating algorithm
$(this).attr('id', new Date().getTime()); 

根据唯一id进行选择:

// getting unique id
var uniqueId = $(this).getUniqueId();

// or you could just get the id:
var uniqueId = $(this).attr('id');

// selecting by id:
var element = $('#' + uniqueId);

// if you decide to use another attribute other than id:
var element = $('[data-unique-id="' + uniqueId + '"]');

【讨论】:

有趣的想法,可能有用:) 这在我重新加载页面后不起作用。我希望能够识别用户在上一个会话中引用的元素 您可以做到这一点的唯一方法是使用 HTML5 存储之类的东西存储数据,或者将其保存在数据库中的服务器上。我们正在做类似的事情。 我不想依赖于我所做的文档更改。我需要一种在不更改源文档的情况下拥有唯一选择器的方法。 如果元素还没有id,这是一个不错的技巧。但是检查元素中现有idif 必须更安全。【参考方案2】:

你可以这样做:

$(".track").click(function() 
  recordEvent($(this).attr("id"));
);

它将onclick 事件处理程序附加到每个具有track 类的对象。每次单击一个对象时,它的 id 都会输入到recordEvent() 函数中。你可以让这个函数记录每个对象的时间和 id 或任何你想要的。

【讨论】:

我得说,将点击事件绑定到页面上的每个元素是一个糟糕的主意。这将导致巨大的性能负载。 我已将其更改为类选择器。想法是一样的。 是否没有有效的方法为页面上的每个元素设置点击处理程序? 有,只需使用$("*").click(),但正如 Eli 所说,这需要在性能上进行权衡。您会惊讶于标准网页上有多少元素。只需尝试添加此 css 规则即可找到:* border: 1px solid red; 这里比较晚,但是在所有内容上放置点击处理程序的最有效方法是在 html 元素上使用delegate()。然后,您可以根据需要过滤掉元素,但这基本上意味着您只有一个单击处理程序,它被委派来处理所有事情。 注意: 自 jQuery v1.7 起,委托已被弃用,支持 on(),但签名非常相似【参考方案3】:
(any element).onclick(function() 
  uniqueSelector = $(this).getUniqueSelector();
)

this IS 唯一的选择器和指向被点击元素的路径。为什么不使用它?您可以利用 jquery 的 $.data() 方法来设置 jquery 选择器。或者,只需推送您将来需要使用的元素:

var elements = [];
(any element).onclick(function() 
  elements.push(this);
)

如果你真的需要xpath,可以用下面的代码计算一下:

  function getXPath(node, path) 
    path = path || [];
    if(node.parentNode) 
      path = getXPath(node.parentNode, path);
    

    if(node.previousSibling) 
      var count = 1;
      var sibling = node.previousSibling
      do 
        if(sibling.nodeType == 1 && sibling.nodeName == node.nodeName) count++;
        sibling = sibling.previousSibling;
       while(sibling);
      if(count == 1) count = null;
     else if(node.nextSibling) 
      var sibling = node.nextSibling;
      do 
        if(sibling.nodeType == 1 && sibling.nodeName == node.nodeName) 
          var count = 1;
          sibling = null;
         else 
          var count = null;
          sibling = sibling.previousSibling;
        
       while(sibling);
    

    if(node.nodeType == 1) 
      path.push(node.nodeName.toLowerCase() + (node.id ? "[@id='"+node.id+"']" : count > 0 ? "["+count+"]" : ''));
    
    return path;
  ;

参考:http://snippets.dzone.com/posts/show/4349

【讨论】:

this 不能在页面重新加载后使用,以在外部应用程序中使用该元素。 +1 xpath 函数【参考方案4】:

你可以做这样的事情(未经测试)

function GetPathToElement(jElem)

   var tmpParent = jElem;
   var result = '';
   while(tmpParent != null)
   
       var tagName = tmpParent.get().tagName;
       var className = tmpParent.get().className;
       var id = tmpParent.get().id;
       if( id != '') result = '#' + id + result;
       if( className !='') result = '.' + className + result;
       result = '>' + tagName + result;
       tmpParent = tmpParent.parent();
    
    return result;

此函数将保存元素的“路径”,现在要在将来再次找到该元素几乎不可能像 html 那样,因为在这个函数中我不保存每个元素的兄弟索引,我只保存 id(s) 和 classes。

因此,除非您的 html 文档的每个元素都有一个 ID,否则这种方法是行不通的。

【讨论】:

问题是more complex than it seems。【参考方案5】:
$(document).ready(function() 
    $("*").click(function(e) 
        var path = [];
        $.each($(this).parents(), function(index, value) 
            var id = $(value).attr("id");
            var class = $(value).attr("class");
            var element = $(value).get(0).tagName
                path.push(element + (id.length > 0 ? " #" + id : (class.length > 0 ? " .": "") + class));
        );
        console.log(path.reverse().join(">"));
        return false;
    );
);

工作示例:http://jsfiddle.net/peeter/YRmr5/

在使用 * 选择器(非常慢)并阻止事件冒泡时,您可能会遇到问题,但如果没有更多的 HTML 代码,您将无法真正提供帮助。

【讨论】:

这是我得到的:Uncaught SyntaxError: Unexpected token =【参考方案6】:

我会自己回答这个问题,因为我找到了一个我必须修改的解决方案。以下脚本正在运行,并且基于 script of Blixt:

jQuery.fn.extend(
    getPath: function () 
        var path, node = this;
        while (node.length) 
            var realNode = node[0], name = realNode.name;
            if (!name) break;
            name = name.toLowerCase();

            var parent = node.parent();

            var sameTagSiblings = parent.children(name);
            if (sameTagSiblings.length > 1)  
                var allSiblings = parent.children();
                var index = allSiblings.index(realNode) + 1;
                if (index > 1) 
                    name += ':nth-child(' + index + ')';
                
            

            path = name + (path ? '>' + path : '');
            node = parent;
        

        return path;
    
);

【讨论】:

+1 不错的解决方案,我改进了该解决方案以实现多个jQuery 返回。看我的回答... 最内层if-clause if (index &gt; 1) /*...*/ 不是错了吗?一定是 if (index > 0) /*...*/ ,不是吗? @Alp -- 我是 jQuery 新手...希望您能告诉我在单击任何项​​目时如何实际调用此函数。听起来我和你有类似的需求,我想跟踪屏幕上点击的内容。您是否有某种侦听器函数正在等待单击以调用此“getpath”函数?您能提供的任何帮助将不胜感激。谢谢! @Mark: 只需创建一个点击监听器并将此方法用作回调 请注意 - 如果您希望选择器对页面结构的变化具有鲁棒性,并且有 10 多个库 (comparison) 实现了此功能,那么问题比看起来更复杂。 【参考方案7】:

此答案不满足原始问题描述,但确实回答了标题问题。我来这个问题是为了寻找一种为元素获取唯一选择器的方法,但我不需要选择器在页面加载之间有效。所以,我的答案在页面加载之间不起作用。

我觉得修改 DOM 并不理想,但它是构建一个没有大量代码的唯一选择器的好方法。看了@Eli的回答,我有了这个想法:

为自定义属性分配一个唯一值。

$(element).attr('secondary_id', new Date().getTime())
var secondary_id = $(element).attr('secondary_id');

然后使用该唯一 ID 构建 CSS 选择器。

var selector = '[secondary_id='+secondary_id+']';

然后你有一个选择器来选择你的元素。

var found_again = $(selector);

许多人想要检查以确保元素上还没有secondary_id 属性。

if ($(element).attr('secondary_id')) 
  $(element).attr('secondary_id', (new Date()).getTime());

var secondary_id = $(element).attr('secondary_id');

把它们放在一起

$.fn.getSelector = function()
  var e = $(this);

  // the `id` attribute *should* be unique.
  if (e.attr('id'))  return '#'+e.attr('id') 

  if (e.attr('secondary_id')) 
    return '[secondary_id='+e.attr('secondary_id')+']'
  

  $(element).attr('secondary_id', (new Date()).getTime());

  return '[secondary_id='+e.attr('secondary_id')+']'
;

var selector = $('*').first().getSelector();

【讨论】:

有10+ libraries为给定的DOM节点生成CSS选择器。 也许现在有成熟、可靠和健壮的库,但在写这个答案的时候还没有。但是,谢谢你的链接。 css-selector-generator 看起来很有趣。【参考方案8】:

与@Alp 的解决方案相同,但兼容多个 jQuery 元素。

jQuery('.some-selector') 可以生成一个或多个 DOM 元素。不幸的是,@Alp 的解决方案仅适用于第一个解决方案。我的解决方案将它们与, 连接起来。

如果您只想处理第一个元素,请这样做:

jQuery('.some-selector').first().getPath();

// or
jQuery('.some-selector:first').getPath();

改进版

jQuery.fn.extend(
    getPath: function() 
        var pathes = [];

        this.each(function(index, element) 
            var path, $node = jQuery(element);

            while ($node.length) 
                var realNode = $node.get(0), name = realNode.localName;
                if (!name)  break; 

                name = name.toLowerCase();
                var parent = $node.parent();
                var sameTagSiblings = parent.children(name);

                if (sameTagSiblings.length > 1)
                
                    var allSiblings = parent.children();
                    var index = allSiblings.index(realNode) + 1;
                    if (index > 0) 
                        name += ':nth-child(' + index + ')';
                    
                

                path = name + (path ? ' > ' + path : '');
                $node = parent;
            

            pathes.push(path);
        );

        return pathes.join(',');
    
);

【讨论】:

如何投票结束这个问题,因为它与您发布the same answer 的问题重复? @DanDascalescu:我不确定问题是否相同。我不想决定这个。我刚刚改进了一个现有的答案...【参考方案9】:

如果你有一个身份属性(例如 id="something"),你应该像这样获取它的值,

var selector = "[id='" + $(yourObject).attr("id") + "']";
console.log(selector); //=> [id='something']
console.log($(selector).length); //=> 1

如果你没有身份属性并且你想获得它的选择器,你可以创建一个身份属性。像上面这样,

var uuid = guid();
$(yourObject).attr("id", uuid); // Set the uuid as id of your object.

您可以使用自己的guid方法,或者使用this中的源代码所以回答,

function guid() 
    function s4() 
        return Math.floor((1 + Math.random()) * 0x10000)
                .toString(16)
                .substring(1);
    
    return s4() + s4() + '-' + s4() + '-' + s4() + '-' +
            s4() + '-' + s4() + s4() + s4();

【讨论】:

【参考方案10】:

您还可以查看findCssSelector。代码在我的other answer。

【讨论】:

【参考方案11】:

虽然问题是针对 jQuery,但在 ES6 中,很容易获得类似于 @Alp 的 Vanilla javascript 的内容(我还添加了几行代码,跟踪 nameCount,以尽量减少 nth-child 的使用):

function getSelectorForElement (elem) 
    let path;
    while (elem) 
        let subSelector = elem.localName;
        if (!subSelector) 
            break;
        
        subSelector = subSelector.toLowerCase();

        const parent = elem.parentElement;

        if (parent) 
            const sameTagSiblings = parent.children;
            if (sameTagSiblings.length > 1) 
                let nameCount = 0;
                const index = [...sameTagSiblings].findIndex((child) => 
                    if (elem.localName === child.localName) 
                        nameCount++;
                    
                    return child === elem;
                ) + 1;
                if (index > 1 && nameCount > 1) 
                    subSelector += ':nth-child(' + index + ')';
                
            
        

        path = subSelector + (path ? '>' + path : '');
        elem = parent;
    
    return path;

【讨论】:

【参考方案12】:

纯 JavaScript 解决方案

注意:这里使用Array.from和Array.prototype.filter,在IE11中都需要polyfill。

function getUniqueSelector(node) 
  let selector = "";
  while (node.parentElement) 
    const siblings = Array.from(node.parentElement.children).filter(
      e => e.tagName === node.tagName
    );
    selector =
      (siblings.indexOf(node)
        ? `$node.tagName:nth-of-type($siblings.indexOf(node) + 1)`
        : `$node.tagName`) + `$selector ? " > " : ""$selector`;
    node = node.parentElement;
  
  return `html > $selector.toLowerCase()`;

用法

getUniqueSelector(document.getElementsByClassName('SectionFour')[0]);

getUniqueSelector(document.getElementById('content'));

【讨论】:

我建议先检查元素是否有id,因为这样我们就可以得到一个非常简单的选择器,而无需遍历 DOM。 @DanielVeihelmann 当然,但这不适用于具有多个同名ids 的代码库。这种情况发生在我身上太多次了【参考方案13】:

我为自己找到了一些修改后的解决方案。我添加到路径选择器#id,.className 并将路径长度剪切到#id:

$.fn.extend(
            getSelectorPath: function () 
                var path,
                    node = this,
                    realNode,
                    name,
                    parent,
                    index,
                    sameTagSiblings,
                    allSiblings,
                    className,
                    classSelector,
                    nestingLevel = true;

                while (node.length && nestingLevel) 
                    realNode = node[0];
                    name = realNode.localName;

                    if (!name) break;

                    name = name.toLowerCase();
                    parent = node.parent();
                    sameTagSiblings = parent.children(name);

                    if (realNode.id) 
                        name += "#" + node[0].id;

                        nestingLevel = false;

                     else if (realNode.className.length) 
                        className =  realNode.className.split(' ');
                        classSelector = '';

                        className.forEach(function (item) 
                            classSelector += '.' + item;
                        );

                        name += classSelector;

                     else if (sameTagSiblings.length > 1) 
                        allSiblings = parent.children();
                        index = allSiblings.index(realNode) + 1;

                        if (index > 1) 
                            name += ':nth-child(' + index + ')';
                        
                    

                    path = name + (path ? '>' + path : '');
                    node = parent;
                

                return path;
            
        );

【讨论】:

【参考方案14】:

使用 jquery 和 typescript 函数式编程获取 dom 路径

function elementDomPath( element: any, selectMany: boolean, depth: number ) 
        const elementType = element.get(0).tagName.toLowerCase();
        if (elementType === 'body') 
            return '';
        

        const id          = element.attr('id');
        const className   = element.attr('class');
        const name = elementType + ((id && `#$id`) || (className && `.$className.split(' ').filter((a: any) => a.trim().length)[0]`) || '');

        const parent = elementType === 'html' ? undefined : element.parent();
        const index = (id || !parent || selectMany) ? '' : ':nth-child(' + (Array.from(element[0].parentNode.children).indexOf(element[0]) + 1) + ')';

        return !parent ? 'html' : (
            elementDomPath(parent, selectMany, depth + 1) +
            ' ' +
            name +
            index
        );
    

【讨论】:

【参考方案15】:

我的原生 JavaScript 函数:

function getUniqueSelector( element ) 
    if (element.id) 
        return '#' + element.id;
     else if (element.tagName === 'BODY') 
        return 'BODY';
     else 
        return `$getUniqueSelector(element.parentElement) > $element.tagName:nth-child($myIndexOf(element))`;
    


function myIndexOf( element ) 
    let index = 1;
    // noinspection JSAssignmentUsedAsCondition
    while (element = element.previousElementSibling) index++;
    return index;

【讨论】:

【参考方案16】:

将 js 元素(节点)传递给此函数.. 工作一点点.. 尝试发布您的 cmets

function getTargetElement_cleanSelector(element)
    let returnCssSelector = '';

    if(element != undefined)


        returnCssSelector += element.tagName //.toLowerCase()

        if(element.className != '')
            returnCssSelector += ('.'+ element.className.split(' ').join('.')) 
        

        if(element.id != '')
            returnCssSelector += ( '#' + element.id ) 
        

        if(document.querySelectorAll(returnCssSelector).length == 1)
            return returnCssSelector;
        

        if(element.name != undefined && element.name.length > 0)
            returnCssSelector += ( '[name="'+ element.name +'"]' ) 
        

        if(document.querySelectorAll(returnCssSelector).length == 1)
            return returnCssSelector;
        

        console.log(returnCssSelector)

        let current_parent = element.parentNode;

        let unique_selector_for_parent = getTargetElement_cleanSelector(current_parent);

        returnCssSelector = ( unique_selector_for_parent + ' > ' + returnCssSelector )

        console.log(returnCssSelector)

        if(document.querySelectorAll(returnCssSelector).length == 1)
            return returnCssSelector;
        

    
    

    return returnCssSelector;

【讨论】:

以上是关于在Jquery中获取元素的唯一选择器的主要内容,如果未能解决你的问题,请参考以下文章

jQuery学习笔记:选择器总结

jQuery学习笔记:选择器总结

jQuery的选择器小总结

jQuery基础--样式篇

JQuery获取元素的方法总结

jquery 选中文本框 jquery 文本框样式 jquery 改变样式