Укороченный querySelector - вызовов функции цепочкой

Хочу написать аналог document.querySelector('selector'), но в одну букву - q('selector'). Реализация выглядит просто:

var q = props => document.querySelector(props)

Но querySelector можно вызывать в цепочке: element.querySelector('sel1').querySelector('sel2'), а сокращенную функцию так вызывать нельзя. Хочется получить q('sel1').q('sel2').q('sel3')

Похожая проблема рассматривалась тут, но там нужно обязательно добавить вызывать value()


Добавление: Обычно укороченную функцию очень удобно использовать, но если объект определен ранее, неплохо было бы сделать "цепочный" вызов от объекта. Как я теперь понял, достаточно, чтобы объектом был только первый элемент, т.е. достаточно вызывать вызывать функцию, у которой "может не быть" первого аргумента. Главное, задать правильный вопрос!

var q = (prop, props = prop) => (prop.length ? document : prop).querySelector(props);

Ответы (3 шт):

Автор решения: GrimCap

Написал даже красивее: q('sel1', 'sel2', 'sel3'), где селекторы ищутся по цепочке. И запись не сильно длиннее получилась:

var q = (...props) => props.reduce((el, prop) => el.querySelector(prop), document)

Немного докрутил, чтобы в качестве первого аргумента можно было использовать объект

var q = (...props) => props.reduce((el, prop) => el.querySelector(prop), props[0].length ? document : props.shift());

Финальная правка ответа. Я понял, что мне нужен только первый элемент, поэтому меня устроит небольшое расширение изначального варианта - q(elem, 'selectors'):

var q = (prop, props = prop) => (prop.length ? document : prop).querySelector(props);
→ Ссылка
Автор решения: ΝNL993

Наконец-то понял что вы хотели, вот реализация :)

class MultiElement {
  constructor(selector, context) {
    if(context === null) {
      throw new Error('Context is null!')
    }

    this.el = (context || document).querySelector(selector)
  }

  q(selector) {
    return new MultiElement(selector, this.el)
  }
}

function q(selector) {
  return new MultiElement(selector)
}

// Пример использования

let element = q('.el1').q('.el2').el

console.log(element.className)
<div class="el2 uncorrect_el2"></div>

<div class="el1">
  <div class="el2">
    <div class="el3">
      <div class="el4"></div>
    </div>
  </div>
</div>

Внимание!

Такой способ не является оптимальным или быстрым, намного проще и быстрее будет использовать CSS селекторы. Вот пример работы с ними:

let element = document.querySelector('.el1 > .el2')

console.log(element.className)
<div class="el1">
  <div class="el2">
    <div class="el3">
      <div class="el4"></div>
    </div>
  </div>
</div>

→ Ссылка
Автор решения: Neverm1ndo

Возможно не лучший вариант, но я все же вкину его сюда, т.к способ все же рабочий и удовлетворяет условиям.

В функции q с помощью querySelector находим нужный элемент. Если этот элемент найден, то добавляем ему метод q и возвращаем этот самый пропатченный элемент.

/**
* @returns {Element | null}
* у Element нет метода Element.q, дальнейший чейнинг с такой реализацией невозможен
*/
// var q = (props) => document.querySelector(props);

/**
* @param {string} - selector
* @returns {Element | null}
* Не использовал стрелочную функцию, т.к она не имеет this и берет его извне
*/
function q(selector) {
  
  const element = this instanceof Window
                ? document.querySelector(selector)
                : this.querySelector(selector);

  element.q = q;
  return element;
}


console.log(q('sel1').q('sel2').q('sel3')) // sel3 flag
console.log(q('sel3')) // sel3 wrong
console.log(q('sel4').q('sel5')); // TypeError
<sel3>wrong</sel3>
<sel1>
  <sel2>
    <sel3>flag</sel3>
  </sel2>
</sel1>

Это, наверное, наименее замороченный способ.

→ Ссылка