Javascript jQuery最近(DOM树内部和外部)

Javascript jQuery最近(DOM树内部和外部),javascript,jquery,Javascript,Jquery,给定一个元素和任何选择器,我需要找到与之匹配的最近的元素,不管它是在元素内部还是外部 目前jQuery不提供这种遍历功能,但有必要。以下是场景: 元素所在的许多项的列表将按照名称的建议进行操作,并查找最接近的项,但正如大家所知,它只向上搜索。在我看来,它的名字应该有所不同 是否有人知道任何自定义遍历函数可以执行上述操作? 谢谢我之所以问你们,是因为肯定有人写了这篇文章,但我很不幸在互联网上找到了线索我认为使用兄弟姐妹选择器~或儿童选择器>可以解决你们的问题无论你们的情况如何 任何内置选择器都不允

给定一个元素和任何选择器,我需要找到与之匹配的最近的元素,不管它是在元素内部还是外部

目前jQuery不提供这种遍历功能,但有必要。以下是场景:

元素所在的许多项的列表将按照名称的建议进行操作,并查找最接近的项,但正如大家所知,它只向上搜索。在我看来,它的名字应该有所不同

是否有人知道任何自定义遍历函数可以执行上述操作?
谢谢我之所以问你们,是因为肯定有人写了这篇文章,但我很不幸在互联网上找到了线索

我认为使用兄弟姐妹选择器~或儿童选择器>可以解决你们的问题无论你们的情况如何

任何内置选择器都不允许在树上下搜索。我确实创建了一个自定义的find这个扩展,允许您执行类似$elementClicked的操作。“li:hasthis按钮”允许您执行类似的操作

// Add findThis method to jQuery (with a custom :this check)
jQuery.fn.findThis = function (selector) {
    // If we have a :this selector
    if (selector.indexOf(':this') > 0) {
        var ret = $();
        for (var i = 0; i < this.length; i++) {
            var el = this[i];
            var id = el.id;
            // If not id already, put in a temp (unique) id
            el.id = 'id' + new Date().getTime();
            var selector2 = selector.replace(':this', '#' + el.id);
            ret = ret.add(jQuery(selector2, document));
            // restore any original id
            el.id = id;
        }
        ret.selector = selector;
        return ret;
    }
    // do a normal find instead
    return this.find(selector);
}

// Test case
$(function () {
    $('a').click(function () {
        $(this).findThis('li:has(:this) button').css({
            "border": '3px solid red'
        });
    });
});
JSFiddle:


注意:点击图片/链接进行测试。

这里是使用我在评论中提到的想法的另一次尝试:

$(this).parents(':has(button):first').find('button').css({
        "border": '3px solid red'
});
JSFiddle:

它基本上会查找包含单击的元素和目标的第一个祖先,然后找到目标

性能:
关于速度,这是在人机交互速度下使用的,即每秒最多几次,因此,如果它以合理明显的方式用最少的代码解决问题,那么作为一个缓慢的选择器是无关紧要的。您必须每秒单击100次才能注意到与快速选择器相比的任何不同:

不久前,我想对完全基于DOM的文本编辑器执行相同的操作,并且需要在树的上下查找上一个ARR LEFT和下一个ARR RIGHT文本节点。基于这段代码,我做了一个适合这个问题的改编。请注意,它的性能相当高,但它可以适应任何场景。有两个函数findDevelopmentNode和findTextElementNode,它们都返回具有属性的对象:

match-返回搜索中最近的匹配节点,如果未找到,则返回FALSE 迭代次数-返回查找节点的迭代次数。这允许您检查上一个节点是否比下一个节点更近,反之亦然。 参数如下:

 //@param {HTMLElement} referenceNode - The node from which to start the search
 //@param {function} truthTest - A function that returns true for the given element
 //@param {HTMLElement} [limitNode=document.body] - The limit up to which to search to
在您的情况下,您可以:

var a = domUtils.findNextElementNode(
    document.getElementsByTagName('a')[0], // known element
     function(node) { return (node.nodeName === 'BUTTON'); }
);
var b = domUtils.findPrevElementNode(
    document.getElementsByTagName('a')[0], // known element
     function(node) { return (node.nodeName === 'BUTTON'); }
);

var result = a.match ? (b.match ? (a.iterations < b.iterations ? a.match : 
  (a.iterations === b.iterations ? fnToHandleEqualDistance() : b.match)) : a.match) : 
  (b.match ? b.match : false); 
看看它。

/ 我已经通过逻辑解决了工作问题,所以我会首先查看元素内部,然后查看它们的兄弟,最后,如果仍然有未找到的项,我会对父元素进行递归搜索

JS代码:
任何内置选择器都不允许在树上进行上下搜索。我确实创建了一个自定义的find这个扩展,允许您执行类似$elementClicked的操作。“li:hasthis按钮”允许您执行类似的操作。你对此感兴趣吗?是的,我知道jquery没有内置的东西,这就是我为什么要问的原因。您的函数是否能够像我预想的那样工作?感谢您,我完全理解所需的解决方案。我只是不明白它解决了什么问题。我不明白为什么一个元素,在DOM树的一个可能完全不相关的部分中,会在语义上链接到原始元素,而没有某种标识属性。如果它像-第一个按钮最接近两个元素-数据-属性,链接元素,如果您的DOM根本没有结构,这将是一个更实用的解决方案。否则,您也可以编写一个自定义搜索。我不想在选择器中包含li,但在本例中它会自动相对于元素。尽可能地将标记与选择器分离possible@vsync. 必须有一些限制,否则你将不得不拖网所有的父母,直到你得到一个匹配,但谁能说一个匹配就足够了。您的代码中可能有某种形式的容器,它可以是广泛的,但除此之外,您基本上是依次搜索所有父级,直到找到一个匹配项。这是相对缓慢的。这就是最接近的工作方式。我想要它做的就是扩展它的功能,这样它就可以首先查看内部,如果没有找到元素,那么就查找DOM。就这些。我在这里假设内部DOM不是很大,首先搜索会更便宜,只有在没有找到的情况下,才会向上遍历。您同意吗?使用单个jQuery Sizzle选择器是不可能的,因为它从右向左工作,不允许向上引用。我知道,在给出上述答案之前,我花了很多时间在Sizzle源代码中试图解决这个问题。“你需要更换Sizzle。@詹姆斯·索普:从祖先那里下来就足够找到一个不需要的共同父母了。最糟糕的情况是,每次搜索都会击中尸体,然后他就有麻烦了:我认为这非常接近——只要按钮位于每个图像相对较近的位置。当然解决了例子giv
Enhat是一个非常慢的选择器:@vsync:从速度上看,你到底期望从你的需求中得到什么?我的另一个答案相当有效,但需要一个起点,即一些共同的祖先元素。至少你现在比以前多了两个选择:请看我的答案,告诉我你的想法。我?这是一个严肃的问题。这不是一个玩笑,你可以简单地投反对票。在我的一生中,我从来没有要求任何人去投票。我觉得你的评论有点不对劲offensive@vsync你在说什么?我的意思很简单,他的一个朋友一定把它连根拔起了,因为它根本不相关。叹气有一天起床是不值得的@真的,对不起,我把评论的意思弄错了。不管怎么说,现在你可以在床上上网,这样你就可以说早上睁开眼睛是不划算的:请看我的答案,答案要多得多simple@vsync您的确实要简单得多,但它也需要jQuery,而2只有一个参数,这是非常好的,因为它可能足够了。顺便说一句,记住我的代码是基于我之前编写的一段代码,用于查找下一个和上一个元素。谢谢,我刚刚看到有一种情况我的代码不够用:当元素在referenceNode中并且有兄弟元素时,这将失败。这个问题毕竟是关于jQuery选择器的,因此,我对第一个参数没有问题:如果我能验证它是否与前面的示例HTML一起工作,这将更加令人印象深刻:您能纠正这个JSFIDLE吗?您自己的JSFIDLE似乎也不起作用。因为这只是在点击速度下,我更关心的是维护/可读性,而不是原始速度。性能不应该成为这里的一个因素:这仍然需要改进,但我觉得这也是删除我的评论的正确方法。使您的代码混乱。注意:如果您仍然要沿着祖先旅行,那么兄弟姐妹检查是多余的。我想评论中已经提到了这一点。因为这是对单击的响应,所以速度根本不是问题。我的简短/简单的回答在一行中做了同样的事情,所以我不知道为什么您会为这么多代码而烦恼。你的电话:我在JSFIDLE中尝试了您的代码,但无法按原样运行:您能修改JSFIDLE吗?一旦开始工作,我们可以尝试一些JSPerf测试,看看代码是否值得付出努力:@TrueBlueAussie-checking@TrueBlueAussie-它不起作用,因为您需要将左侧的代码执行设置为onDomReady或onLoad,因为如果它在头部,它将找不到任何元素。请问,为什么您更喜欢JSFIDLE而不是jsBin?
$(this).parents(':has(button):first').find('button').css({
        "border": '3px solid red'
});
 //@param {HTMLElement} referenceNode - The node from which to start the search
 //@param {function} truthTest - A function that returns true for the given element
 //@param {HTMLElement} [limitNode=document.body] - The limit up to which to search to
var domUtils = {
    findPrevElementNode: function(referenceNode, truthTest, limitNode) {
        var element = 1, 
            iterations = 0,
            limit = limitNode || document.body,
            node = referenceNode;
        while (!truthTest(node) && node !== limit) {
            if (node.previousSibling) {
                node = node.previousSibling;
                iterations++;
                if (node.lastChild) {
                    while (node.lastChild) {
                        node = node.lastChild;
                        iterations++;
                    }
                }
            } else {
                if (node.parentNode) {
                    node = node.parentNode;
                    iterations++;
                } else {
                    return false;
                }
            }
        }
        return {match: node === limit ? false : node, iterations: iterations};
    },
    findNextElementNode: function(referenceNode, truthTest, limitNode) {
        var element = 1, 
            iterations = 0,
            limit = limitNode || document.body,
            node = referenceNode;
        while (!truthTest(node) && node !== limit) {
            if (node.nextSibling) {
                node = node.nextSibling;
                iterations++;
                if (node.firstChild) {
                    while (node.firstChild) {
                        node = node.firstChild;
                        iterations++;
                    }
                }
            } else {
                if (node.parentNode) {
                    node = node.parentNode;
                    iterations++;
                } else {
                    return false;
                }
            }
        }
        return {match: node === limit ? false : node, iterations: iterations};
    }
};
var a = domUtils.findNextElementNode(
    document.getElementsByTagName('a')[0], // known element
     function(node) { return (node.nodeName === 'BUTTON'); }
);
var b = domUtils.findPrevElementNode(
    document.getElementsByTagName('a')[0], // known element
     function(node) { return (node.nodeName === 'BUTTON'); }
);

var result = a.match ? (b.match ? (a.iterations < b.iterations ? a.match : 
  (a.iterations === b.iterations ? fnToHandleEqualDistance() : b.match)) : a.match) : 
  (b.match ? b.match : false); 
jQuery.fn.findClosest = function (selector) {
    // If we have a :this selector
    var output = $(), 
        down = this.find(selector),
        siblings,
        recSearch,
        foundCount = 0;

    if(down.length) {
        output = output.add(down);
        foundCount += down.length;
    }

    // if all elements were found, return at this point
    if( foundCount == this.length )
      return output;

    siblings = this.siblings(selector);

    if( siblings.length) {
        output = output.add(siblings);
        foundCount += siblings.length;
    }
    
    // this is the expensive search path if there are still unfound elements

    if(foundCount < this.length){
        recSearch = rec(this.parent().parent());
        if( recSearch )
            output = output.add(recSearch);
        
    }

    function rec(elm){
        var result = elm.find(selector);
        if( result.length )
          return result;
        else
          rec(elm.parent());
    }

    return output;
};

// Test case

var buttons = $('a').findClosest('button');

console.log(buttons);

buttons.click(function(){
  this.style.outline = "1px solid red";
})