Проверьте event.target.parentElement с помощью matchSelector js

Не беспокоит резервный вариант старого браузера. Кроме того, нельзя использовать библиотеки.

У меня есть объект события. Я тестирую event.target против селектора css через matchSelector:

event['target'].matchesSelector('css selector here');

это работает, как и:

event['target']['parentElement'].matchesSelector('css selector here');

...а также:

event['target']['parentElement']['parentElement'].matchesSelector('css selector here');

То, что я ищу, - это какой-то возможный объектный метод за пределами моего понимания, который я мог бы использовать для проверки каждого parentElement на совпадение без цикла for. Я сосредоточен на эффективности.

Спасибо!


person Randy Hall    schedule 19.10.2012    source источник
comment
Просто мысль ... возможно, сгладить родителей event.target в какой-то объект, который можно было бы запросить?   -  person Randy Hall    schedule 19.10.2012
comment
Почему вы избегаете цикла for? Также подойдет цикл while или, возможно, рекурсия, но я не уверен, зачем вам это нужно.   -  person pimvdb    schedule 19.10.2012
comment
@pimvdb Для абсолютной максимальной эффективности встроенный в браузер способ одновременного доступа и сопоставления со свойствами объекта события.   -  person Randy Hall    schedule 19.10.2012
comment
Технически использование document.querySelectorAll('...'), а затем indexOf в списке узлов не требует matchesSelector каждый раз. Но сомневаюсь, что будет быстрее. matchesSelector определяется только для элементов, а не для списков узлов, поэтому вы не можете избежать какого-либо цикла.   -  person pimvdb    schedule 19.10.2012


Ответы (4)


Чтобы предотвратить избыточный цикл по всем родительским элементам вашего целевого элемента, вы можете выполнить быструю проверку того, находится ли ваш элемент внутри элемента, который соответствует вашему селектору, используя matchesSelector() с селектором, который представляет собой конкатенацию вашего исходного селектора и добавленного селектора контекста, состоящего из пробела и имя тега вашего целевого элемента:

function getAncestorBySelector(elem, selector) {
    if (!elem.matchesSelector(selector + ' ' + elem.tagName)) {
        // If element is not inside needed element, returning immediately.
        return null;
    }

    // Loop for finding an ancestor element that matches your selector.
}
person Marat Tanalin    schedule 17.12.2012
comment
Предложение использовать elem.matches и сделать его совместимым с разными браузерами с помощью следующего полифилла: developer.mozilla.org/en/docs/Web/API/Element/matches - person Pancho; 28.07.2016

Функция closest() сделает то, что вам нужно.

Он начинается с самого элемента и проходит через родителей (направляясь к корню документа), пока не найдет узел, соответствующий предоставленному selectorString. Несколько похоже на функцию jQuery parents().

Таким образом, ваш код будет выглядеть так:

event.target.closest(selectorString)
person Bassem    schedule 19.09.2019
comment
Ничего себе, это именно то, что люди должны использовать. Отличная работа! - person Christopher; 22.10.2019

Решение Марата Таналина подходит, но стандартный синтаксис для elem.matchesSelectorelem.matches :

function getAncestorBySelector(elem, selector) {
    if (!elem.matches(selector + ' ' + elem.tagName)) {
        // If element is not inside needed element, returning immediately.
        return null;
    }

    // Loop for finding an ancestor element that matches your selector.
}

К сожалению, пока не все браузеры поддерживают этот синтаксис. Некоторые браузеры до сих пор реализуют префиксную версию elem.matchesSelector, а Opera Mini вообще не поддерживает эту функцию.

Чтобы решить эту проблему, вы можете объединить описанный подход со следующим полифилл, как предложено MDN :

if (!Element.prototype.matches) {
    Element.prototype.matches = 
        Element.prototype.matchesSelector || 
        Element.prototype.mozMatchesSelector ||
        Element.prototype.msMatchesSelector || 
        Element.prototype.oMatchesSelector || 
        Element.prototype.webkitMatchesSelector ||
        function(s) {
            var matches = (this.document || this.ownerDocument).querySelectorAll(s),
                i = matches.length;
            while (--i >= 0 && matches.item(i) !== this) {}
            return i > -1;            
        };
}

В качестве альтернативы вы можете полностью отказаться от elem.matchesSelector и использовать что-то вроде этого:

function getAncestorBySelector(elem, selector) {
    if([].indexOf.call(document.querySelectorAll(selector + ' ' + elem.tagName), elem) === -1) {
        // If element is not inside needed element, returning immediately.
        return null;
    }
    // Loop for finding an ancestor element that matches your selector.
}

Обе реализации требуют как минимум поддержки querySelectorAll, которая поддерживается всеми современными браузерами.

Поддержка браузера:

введите здесь описание изображения

person John Slegers    schedule 03.05.2016
comment
Просто отметим, что element.matches можно легко сделать кросс-браузерным, используя Polyfill, предоставленный Mozilla. смотри мой комментарий выше - person Pancho; 28.07.2016
comment
@Pancho: Это действительно разумный подход. Я обновил свой ответ, включив ваше предложение в качестве альтернативной реализации. - person John Slegers; 28.07.2016

Большинство вопросов, которые требуют повторной реализации метода jQuery parents() в vanilla-js, закрыты как дубликаты этого вопроса, я хотел предоставить более современный пример, который лучше эмулирует поведение jQuery.

Машинопись:

function getAllParentElements(child: HTMLElement, selector: string = '*') {
    const parents: HTMLElement[] = [];

    let parent: HTMLElement = child.parentElement?.closest(selector);
    while (parent) {
        parents.push(parent);
        parent = parent.parentElement?.closest(selector);
    }

    return parents;
}

Javascript:

function getAllParentElements(child, selector = '*') {
    const parents = [];

    let parent = child.parentElement && child.parentElement.closest(selector);
    while (parent) {
        parents.push(parent);
        parent = parent.parentElement && parent.parentElement.closest(selector);
    }

    return parents;
}
getAllParentElements(childEl); // -> return all ancestors including body & html elements
getAllParentElements(childEl, '.foo'); // -> return all ancestors with `.foo` class
person JaredMcAteer    schedule 08.09.2020